mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add frontend folder
This commit is contained in:
8
frontend/rust-lib/.cargo/config.toml
Normal file
8
frontend/rust-lib/.cargo/config.toml
Normal file
@ -0,0 +1,8 @@
|
||||
#[build]
|
||||
#target-dir = "./bin"
|
||||
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags=["-C", "link-arg=-mmacosx-version-min=10.11"]
|
||||
|
||||
[target.aarch64-apple-darwin]
|
||||
rustflags=["-C", "link-arg=-mmacosx-version-min=10.11"]
|
15
frontend/rust-lib/.gitignore
vendored
Normal file
15
frontend/rust-lib/.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
**/**/*.log*
|
||||
**/**/temp
|
||||
bin/
|
||||
|
||||
.idea/
|
28
frontend/rust-lib/Cargo.toml
Normal file
28
frontend/rust-lib/Cargo.toml
Normal file
@ -0,0 +1,28 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"lib-dispatch",
|
||||
"flowy-sdk",
|
||||
"dart-ffi",
|
||||
"lib-log",
|
||||
"flowy-user",
|
||||
"flowy-user-infra",
|
||||
"flowy-ast",
|
||||
"flowy-derive",
|
||||
"flowy-test",
|
||||
"lib-sqlite",
|
||||
"flowy-database",
|
||||
"lib-infra",
|
||||
"flowy-workspace",
|
||||
"flowy-workspace-infra",
|
||||
"dart-notify",
|
||||
"flowy-document",
|
||||
"flowy-document-infra",
|
||||
"lib-ot",
|
||||
"lib-ws",
|
||||
"backend-service",
|
||||
]
|
||||
|
||||
exclude = ["../backend"]
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
30
frontend/rust-lib/backend-service/Cargo.toml
Normal file
30
frontend/rust-lib/backend-service/Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "backend-service"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
flowy-workspace-infra = { path = "../flowy-workspace-infra" }
|
||||
flowy-user-infra = { path = "../flowy-user-infra" }
|
||||
|
||||
log = "0.4.14"
|
||||
lazy_static = "1.4.0"
|
||||
tokio = { version = "1", features = ["rt"] }
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0.24"
|
||||
bytes = { version = "1.0", features = ["serde"]}
|
||||
reqwest = "0.11"
|
||||
hyper = "0.14"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_repr = "0.1"
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
protobuf = {version = "2.18.0"}
|
||||
derive_more = {version = "0.99", features = ["display"]}
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
actix-web = {version = "4.0.0-beta.8", optional = true}
|
||||
|
||||
[features]
|
||||
http_server = ["actix-web"]
|
53
frontend/rust-lib/backend-service/src/config.rs
Normal file
53
frontend/rust-lib/backend-service/src/config.rs
Normal file
@ -0,0 +1,53 @@
|
||||
pub const HOST: &'static str = "localhost:8000";
|
||||
pub const HTTP_SCHEMA: &'static str = "http";
|
||||
pub const WS_SCHEMA: &'static str = "ws";
|
||||
pub const HEADER_TOKEN: &'static str = "token";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerConfig {
|
||||
http_schema: String,
|
||||
host: String,
|
||||
ws_schema: String,
|
||||
}
|
||||
|
||||
impl std::default::Default for ServerConfig {
|
||||
fn default() -> Self {
|
||||
ServerConfig {
|
||||
http_schema: HTTP_SCHEMA.to_string(),
|
||||
host: HOST.to_string(),
|
||||
ws_schema: WS_SCHEMA.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerConfig {
|
||||
pub fn new(host: &str, http_schema: &str, ws_schema: &str) -> Self {
|
||||
Self {
|
||||
http_schema: http_schema.to_owned(),
|
||||
host: host.to_owned(),
|
||||
ws_schema: ws_schema.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn scheme(&self) -> String { format!("{}://", self.http_schema) }
|
||||
|
||||
pub fn sign_up_url(&self) -> String { format!("{}{}/api/register", self.scheme(), self.host) }
|
||||
|
||||
pub fn sign_in_url(&self) -> String { format!("{}{}/api/auth", self.scheme(), self.host) }
|
||||
|
||||
pub fn sign_out_url(&self) -> String { format!("{}{}/api/auth", self.scheme(), self.host) }
|
||||
|
||||
pub fn user_profile_url(&self) -> String { format!("{}{}/api/user", self.scheme(), self.host) }
|
||||
|
||||
pub fn workspace_url(&self) -> String { format!("{}{}/api/workspace", self.scheme(), self.host) }
|
||||
|
||||
pub fn app_url(&self) -> String { format!("{}{}/api/app", self.scheme(), self.host) }
|
||||
|
||||
pub fn view_url(&self) -> String { format!("{}{}/api/view", self.scheme(), self.host) }
|
||||
|
||||
pub fn doc_url(&self) -> String { format!("{}{}/api/doc", self.scheme(), self.host) }
|
||||
|
||||
pub fn trash_url(&self) -> String { format!("{}{}/api/trash", self.scheme(), self.host) }
|
||||
|
||||
pub fn ws_addr(&self) -> String { format!("{}://{}/ws", self.ws_schema, self.host) }
|
||||
}
|
123
frontend/rust-lib/backend-service/src/errors.rs
Normal file
123
frontend/rust-lib/backend-service/src/errors.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use bytes::Bytes;
|
||||
use serde::{Deserialize, Serialize, __private::Formatter};
|
||||
use serde_repr::*;
|
||||
use std::{fmt, fmt::Debug};
|
||||
|
||||
use crate::response::FlowyResponse;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ServerError>;
|
||||
|
||||
#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ServerError {
|
||||
pub code: ErrorCode,
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
macro_rules! static_error {
|
||||
($name:ident, $status:expr) => {
|
||||
#[allow(non_snake_case, missing_docs)]
|
||||
pub fn $name() -> ServerError {
|
||||
ServerError {
|
||||
code: $status,
|
||||
msg: format!("{}", $status),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl ServerError {
|
||||
static_error!(internal, ErrorCode::InternalError);
|
||||
static_error!(http, ErrorCode::HttpError);
|
||||
static_error!(payload_none, ErrorCode::PayloadUnexpectedNone);
|
||||
static_error!(unauthorized, ErrorCode::UserUnauthorized);
|
||||
static_error!(password_not_match, ErrorCode::PasswordNotMatch);
|
||||
static_error!(params_invalid, ErrorCode::ParamsInvalid);
|
||||
static_error!(connect_timeout, ErrorCode::ConnectTimeout);
|
||||
static_error!(connect_close, ErrorCode::ConnectClose);
|
||||
static_error!(connect_cancel, ErrorCode::ConnectCancel);
|
||||
static_error!(connect_refused, ErrorCode::ConnectRefused);
|
||||
static_error!(record_not_found, ErrorCode::RecordNotFound);
|
||||
|
||||
pub fn new(msg: String, code: ErrorCode) -> Self { Self { code, msg } }
|
||||
|
||||
pub fn context<T: Debug>(mut self, error: T) -> Self {
|
||||
self.msg = format!("{:?}", error);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_record_not_found(&self) -> bool { self.code == ErrorCode::RecordNotFound }
|
||||
|
||||
pub fn is_unauthorized(&self) -> bool { self.code == ErrorCode::UserUnauthorized }
|
||||
}
|
||||
|
||||
pub fn internal_error<T>(e: T) -> ServerError
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
ServerError::internal().context(e)
|
||||
}
|
||||
|
||||
pub fn invalid_params<T: Debug>(e: T) -> ServerError { ServerError::params_invalid().context(e) }
|
||||
|
||||
impl std::fmt::Display for ServerError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let msg = format!("{:?}:{}", self.code, self.msg);
|
||||
f.write_str(&msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&ServerError> for FlowyResponse {
|
||||
fn from(error: &ServerError) -> Self {
|
||||
FlowyResponse {
|
||||
data: Bytes::from(vec![]),
|
||||
error: Some(error.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Clone, derive_more::Display)]
|
||||
#[repr(u16)]
|
||||
pub enum ErrorCode {
|
||||
#[display(fmt = "Unauthorized")]
|
||||
UserUnauthorized = 1,
|
||||
#[display(fmt = "Payload too large")]
|
||||
PayloadOverflow = 2,
|
||||
#[display(fmt = "Payload deserialize failed")]
|
||||
PayloadSerdeFail = 3,
|
||||
#[display(fmt = "Unexpected empty payload")]
|
||||
PayloadUnexpectedNone = 4,
|
||||
#[display(fmt = "Params is invalid")]
|
||||
ParamsInvalid = 5,
|
||||
|
||||
#[display(fmt = "Protobuf serde error")]
|
||||
ProtobufError = 10,
|
||||
#[display(fmt = "Json serde Error")]
|
||||
SerdeError = 11,
|
||||
|
||||
#[display(fmt = "Email address already exists")]
|
||||
EmailAlreadyExists = 50,
|
||||
|
||||
#[display(fmt = "Username and password do not match")]
|
||||
PasswordNotMatch = 51,
|
||||
|
||||
#[display(fmt = "Connect refused")]
|
||||
ConnectRefused = 100,
|
||||
|
||||
#[display(fmt = "Connection timeout")]
|
||||
ConnectTimeout = 101,
|
||||
#[display(fmt = "Connection closed")]
|
||||
ConnectClose = 102,
|
||||
#[display(fmt = "Connection canceled")]
|
||||
ConnectCancel = 103,
|
||||
|
||||
#[display(fmt = "Sql error")]
|
||||
SqlError = 200,
|
||||
#[display(fmt = "Record not found")]
|
||||
RecordNotFound = 201,
|
||||
|
||||
#[display(fmt = "Http request error")]
|
||||
HttpError = 300,
|
||||
|
||||
#[display(fmt = "Internal error")]
|
||||
InternalError = 1000,
|
||||
}
|
7
frontend/rust-lib/backend-service/src/lib.rs
Normal file
7
frontend/rust-lib/backend-service/src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub mod config;
|
||||
pub mod errors;
|
||||
pub mod middleware;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod user_request;
|
||||
pub mod workspace_request;
|
39
frontend/rust-lib/backend-service/src/middleware.rs
Normal file
39
frontend/rust-lib/backend-service/src/middleware.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use crate::{request::ResponseMiddleware, response::FlowyResponse};
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::broadcast;
|
||||
lazy_static! {
|
||||
pub static ref BACKEND_API_MIDDLEWARE: Arc<WorkspaceMiddleware> = Arc::new(WorkspaceMiddleware::new());
|
||||
}
|
||||
|
||||
pub struct WorkspaceMiddleware {
|
||||
invalid_token_sender: broadcast::Sender<String>,
|
||||
}
|
||||
|
||||
impl WorkspaceMiddleware {
|
||||
fn new() -> Self {
|
||||
let (sender, _) = broadcast::channel(10);
|
||||
WorkspaceMiddleware {
|
||||
invalid_token_sender: sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalid_token_subscribe(&self) -> broadcast::Receiver<String> { self.invalid_token_sender.subscribe() }
|
||||
}
|
||||
|
||||
impl ResponseMiddleware for WorkspaceMiddleware {
|
||||
fn receive_response(&self, token: &Option<String>, response: &FlowyResponse) {
|
||||
if let Some(error) = &response.error {
|
||||
if error.is_unauthorized() {
|
||||
log::error!("user is unauthorized");
|
||||
match token {
|
||||
None => {},
|
||||
Some(token) => match self.invalid_token_sender.send(token.clone()) {
|
||||
Ok(_) => {},
|
||||
Err(e) => log::error!("{:?}", e),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
frontend/rust-lib/backend-service/src/request/mod.rs
Normal file
3
frontend/rust-lib/backend-service/src/request/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod request;
|
||||
|
||||
pub use request::*;
|
205
frontend/rust-lib/backend-service/src/request/request.rs
Normal file
205
frontend/rust-lib/backend-service/src/request/request.rs
Normal file
@ -0,0 +1,205 @@
|
||||
use crate::{config::HEADER_TOKEN, errors::ServerError, response::FlowyResponse};
|
||||
use bytes::Bytes;
|
||||
use hyper::http;
|
||||
use protobuf::ProtobufError;
|
||||
use reqwest::{header::HeaderMap, Client, Method, Response};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
pub trait ResponseMiddleware {
|
||||
fn receive_response(&self, token: &Option<String>, response: &FlowyResponse);
|
||||
}
|
||||
|
||||
pub struct HttpRequestBuilder {
|
||||
url: String,
|
||||
body: Option<Bytes>,
|
||||
response: Option<Bytes>,
|
||||
headers: HeaderMap,
|
||||
method: Method,
|
||||
middleware: Vec<Arc<dyn ResponseMiddleware + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl HttpRequestBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
url: "".to_owned(),
|
||||
body: None,
|
||||
response: None,
|
||||
headers: HeaderMap::new(),
|
||||
method: Method::GET,
|
||||
middleware: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn middleware<T>(mut self, middleware: Arc<T>) -> Self
|
||||
where
|
||||
T: 'static + ResponseMiddleware + Send + Sync,
|
||||
{
|
||||
self.middleware.push(middleware);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get(mut self, url: &str) -> Self {
|
||||
self.url = url.to_owned();
|
||||
self.method = Method::GET;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn post(mut self, url: &str) -> Self {
|
||||
self.url = url.to_owned();
|
||||
self.method = Method::POST;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn patch(mut self, url: &str) -> Self {
|
||||
self.url = url.to_owned();
|
||||
self.method = Method::PATCH;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn delete(mut self, url: &str) -> Self {
|
||||
self.url = url.to_owned();
|
||||
self.method = Method::DELETE;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn header(mut self, key: &'static str, value: &str) -> Self {
|
||||
self.headers.insert(key, value.parse().unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn protobuf<T>(self, body: T) -> Result<Self, ServerError>
|
||||
where
|
||||
T: TryInto<Bytes, Error = ProtobufError>,
|
||||
{
|
||||
let body: Bytes = body.try_into()?;
|
||||
self.bytes(body)
|
||||
}
|
||||
|
||||
pub fn bytes(mut self, body: Bytes) -> Result<Self, ServerError> {
|
||||
self.body = Some(body);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub async fn send(self) -> Result<(), ServerError> {
|
||||
let _ = self.inner_send().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn response<T>(self) -> Result<T, ServerError>
|
||||
where
|
||||
T: TryFrom<Bytes, Error = ProtobufError>,
|
||||
{
|
||||
let builder = self.inner_send().await?;
|
||||
match builder.response {
|
||||
None => Err(unexpected_empty_payload(&builder.url)),
|
||||
Some(data) => Ok(T::try_from(data)?),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn option_response<T>(self) -> Result<Option<T>, ServerError>
|
||||
where
|
||||
T: TryFrom<Bytes, Error = ProtobufError>,
|
||||
{
|
||||
let result = self.inner_send().await;
|
||||
match result {
|
||||
Ok(builder) => match builder.response {
|
||||
None => Err(unexpected_empty_payload(&builder.url)),
|
||||
Some(data) => Ok(Some(T::try_from(data)?)),
|
||||
},
|
||||
Err(error) => match error.is_record_not_found() {
|
||||
true => Ok(None),
|
||||
false => Err(error),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn token(&self) -> Option<String> {
|
||||
match self.headers.get(HEADER_TOKEN) {
|
||||
None => None,
|
||||
Some(header) => match header.to_str() {
|
||||
Ok(val) => Some(val.to_owned()),
|
||||
Err(_) => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn inner_send(mut self) -> Result<Self, ServerError> {
|
||||
let (tx, rx) = oneshot::channel::<Result<Response, _>>();
|
||||
let url = self.url.clone();
|
||||
let body = self.body.take();
|
||||
let method = self.method.clone();
|
||||
let headers = self.headers.clone();
|
||||
|
||||
// reqwest client is not 'Sync' by channel is.
|
||||
tokio::spawn(async move {
|
||||
let client = default_client();
|
||||
let mut builder = client.request(method.clone(), url).headers(headers);
|
||||
if let Some(body) = body {
|
||||
builder = builder.body(body);
|
||||
}
|
||||
|
||||
let response = builder.send().await;
|
||||
let _ = tx.send(response);
|
||||
});
|
||||
|
||||
let response = rx.await??;
|
||||
tracing::trace!("Http Response: {:?}", response);
|
||||
let flowy_response = flowy_response_from(response).await?;
|
||||
let token = self.token();
|
||||
self.middleware.iter().for_each(|middleware| {
|
||||
middleware.receive_response(&token, &flowy_response);
|
||||
});
|
||||
match flowy_response.error {
|
||||
None => {
|
||||
self.response = Some(flowy_response.data);
|
||||
Ok(self)
|
||||
},
|
||||
Some(error) => Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unexpected_empty_payload(url: &str) -> ServerError {
|
||||
let msg = format!("Request: {} receives unexpected empty payload", url);
|
||||
ServerError::payload_none().context(msg)
|
||||
}
|
||||
|
||||
async fn flowy_response_from(original: Response) -> Result<FlowyResponse, ServerError> {
|
||||
let bytes = original.bytes().await?;
|
||||
let response: FlowyResponse = serde_json::from_slice(&bytes)?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn get_response_data(original: Response) -> Result<Bytes, ServerError> {
|
||||
if original.status() == http::StatusCode::OK {
|
||||
let bytes = original.bytes().await?;
|
||||
let response: FlowyResponse = serde_json::from_slice(&bytes)?;
|
||||
match response.error {
|
||||
None => Ok(response.data),
|
||||
Some(error) => Err(error),
|
||||
}
|
||||
} else {
|
||||
Err(ServerError::http().context(original))
|
||||
}
|
||||
}
|
||||
|
||||
fn default_client() -> Client {
|
||||
let result = reqwest::Client::builder()
|
||||
.connect_timeout(Duration::from_millis(500))
|
||||
.timeout(Duration::from_secs(5))
|
||||
.build();
|
||||
|
||||
match result {
|
||||
Ok(client) => client,
|
||||
Err(e) => {
|
||||
log::error!("Create reqwest client failed: {}", e);
|
||||
reqwest::Client::new()
|
||||
},
|
||||
}
|
||||
}
|
6
frontend/rust-lib/backend-service/src/response/mod.rs
Normal file
6
frontend/rust-lib/backend-service/src/response/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
mod response;
|
||||
|
||||
#[cfg(feature = "http_server")]
|
||||
mod response_http;
|
||||
|
||||
pub use response::*;
|
86
frontend/rust-lib/backend-service/src/response/response.rs
Normal file
86
frontend/rust-lib/backend-service/src/response/response.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use crate::errors::{ErrorCode, ServerError};
|
||||
use bytes::Bytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{convert::TryInto, error::Error, fmt::Debug};
|
||||
use tokio::sync::oneshot::error::RecvError;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct FlowyResponse {
|
||||
pub data: Bytes,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<ServerError>,
|
||||
}
|
||||
|
||||
impl FlowyResponse {
|
||||
pub fn new(data: Bytes, error: Option<ServerError>) -> Self { FlowyResponse { data, error } }
|
||||
|
||||
pub fn success() -> Self { Self::new(Bytes::new(), None) }
|
||||
|
||||
pub fn data<T: TryInto<Bytes, Error = protobuf::ProtobufError>>(mut self, data: T) -> Result<Self, ServerError> {
|
||||
let bytes: Bytes = data.try_into()?;
|
||||
self.data = bytes;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn pb<T: ::protobuf::Message>(mut self, data: T) -> Result<Self, ServerError> {
|
||||
let bytes: Bytes = Bytes::from(data.write_to_bytes()?);
|
||||
self.data = bytes;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<protobuf::ProtobufError> for ServerError {
|
||||
fn from(e: protobuf::ProtobufError) -> Self { ServerError::internal().context(e) }
|
||||
}
|
||||
|
||||
impl std::convert::From<RecvError> for ServerError {
|
||||
fn from(error: RecvError) -> Self { ServerError::internal().context(error) }
|
||||
}
|
||||
|
||||
impl std::convert::From<serde_json::Error> for ServerError {
|
||||
fn from(e: serde_json::Error) -> Self { ServerError::internal().context(e) }
|
||||
}
|
||||
|
||||
impl std::convert::From<anyhow::Error> for ServerError {
|
||||
fn from(error: anyhow::Error) -> Self { ServerError::internal().context(error) }
|
||||
}
|
||||
|
||||
impl std::convert::From<reqwest::Error> for ServerError {
|
||||
fn from(error: reqwest::Error) -> Self {
|
||||
if error.is_timeout() {
|
||||
return ServerError::connect_timeout().context(error);
|
||||
}
|
||||
|
||||
if error.is_request() {
|
||||
let hyper_error: Option<&hyper::Error> = error.source().unwrap().downcast_ref();
|
||||
return match hyper_error {
|
||||
None => ServerError::connect_refused().context(error),
|
||||
Some(hyper_error) => {
|
||||
let mut code = ErrorCode::InternalError;
|
||||
let msg = format!("{}", error);
|
||||
if hyper_error.is_closed() {
|
||||
code = ErrorCode::ConnectClose;
|
||||
}
|
||||
|
||||
if hyper_error.is_connect() {
|
||||
code = ErrorCode::ConnectRefused;
|
||||
}
|
||||
|
||||
if hyper_error.is_canceled() {
|
||||
code = ErrorCode::ConnectCancel;
|
||||
}
|
||||
|
||||
if hyper_error.is_timeout() {}
|
||||
|
||||
ServerError { code, msg }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ServerError::internal().context(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<uuid::Error> for ServerError {
|
||||
fn from(e: uuid::Error) -> Self { ServerError::internal().context(e) }
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
use crate::response::*;
|
||||
use actix_web::{error::ResponseError, HttpResponse};
|
||||
|
||||
use crate::errors::ServerError;
|
||||
use actix_web::body::AnyBody;
|
||||
|
||||
impl ResponseError for ServerError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let response: FlowyResponse = self.into();
|
||||
response.into()
|
||||
}
|
||||
}
|
||||
impl std::convert::Into<HttpResponse> for FlowyResponse {
|
||||
fn into(self) -> HttpResponse { HttpResponse::Ok().json(self) }
|
||||
}
|
||||
|
||||
impl std::convert::Into<AnyBody> for FlowyResponse {
|
||||
fn into(self) -> AnyBody {
|
||||
match serde_json::to_string(&self) {
|
||||
Ok(body) => AnyBody::from(body),
|
||||
Err(_) => AnyBody::Empty,
|
||||
}
|
||||
}
|
||||
}
|
52
frontend/rust-lib/backend-service/src/user_request.rs
Normal file
52
frontend/rust-lib/backend-service/src/user_request.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use crate::{config::HEADER_TOKEN, errors::ServerError, request::HttpRequestBuilder};
|
||||
use flowy_user_infra::entities::prelude::*;
|
||||
|
||||
pub(crate) fn request_builder() -> HttpRequestBuilder {
|
||||
HttpRequestBuilder::new().middleware(crate::middleware::BACKEND_API_MIDDLEWARE.clone())
|
||||
}
|
||||
|
||||
pub async fn user_sign_up_request(params: SignUpParams, url: &str) -> Result<SignUpResponse, ServerError> {
|
||||
let response = request_builder()
|
||||
.post(&url.to_owned())
|
||||
.protobuf(params)?
|
||||
.response()
|
||||
.await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn user_sign_in_request(params: SignInParams, url: &str) -> Result<SignInResponse, ServerError> {
|
||||
let response = request_builder()
|
||||
.post(&url.to_owned())
|
||||
.protobuf(params)?
|
||||
.response()
|
||||
.await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn user_sign_out_request(token: &str, url: &str) -> Result<(), ServerError> {
|
||||
let _ = request_builder()
|
||||
.delete(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_user_profile_request(token: &str, url: &str) -> Result<UserProfile, ServerError> {
|
||||
let user_profile = request_builder()
|
||||
.get(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.response()
|
||||
.await?;
|
||||
Ok(user_profile)
|
||||
}
|
||||
|
||||
pub async fn update_user_profile_request(token: &str, params: UpdateUserParams, url: &str) -> Result<(), ServerError> {
|
||||
let _ = request_builder()
|
||||
.patch(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
172
frontend/rust-lib/backend-service/src/workspace_request.rs
Normal file
172
frontend/rust-lib/backend-service/src/workspace_request.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use crate::{config::HEADER_TOKEN, errors::ServerError, request::HttpRequestBuilder};
|
||||
use flowy_workspace_infra::entities::prelude::*;
|
||||
|
||||
pub(crate) fn request_builder() -> HttpRequestBuilder {
|
||||
HttpRequestBuilder::new().middleware(crate::middleware::BACKEND_API_MIDDLEWARE.clone())
|
||||
}
|
||||
|
||||
pub async fn create_workspace_request(
|
||||
token: &str,
|
||||
params: CreateWorkspaceParams,
|
||||
url: &str,
|
||||
) -> Result<Workspace, ServerError> {
|
||||
let workspace = request_builder()
|
||||
.post(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.response()
|
||||
.await?;
|
||||
Ok(workspace)
|
||||
}
|
||||
|
||||
pub async fn read_workspaces_request(
|
||||
token: &str,
|
||||
params: WorkspaceIdentifier,
|
||||
url: &str,
|
||||
) -> Result<RepeatedWorkspace, ServerError> {
|
||||
let repeated_workspace = request_builder()
|
||||
.get(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.response::<RepeatedWorkspace>()
|
||||
.await?;
|
||||
|
||||
Ok(repeated_workspace)
|
||||
}
|
||||
|
||||
pub async fn update_workspace_request(
|
||||
token: &str,
|
||||
params: UpdateWorkspaceParams,
|
||||
url: &str,
|
||||
) -> Result<(), ServerError> {
|
||||
let _ = request_builder()
|
||||
.patch(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_workspace_request(token: &str, params: WorkspaceIdentifier, url: &str) -> Result<(), ServerError> {
|
||||
let _ = request_builder()
|
||||
.delete(url)
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// App
|
||||
pub async fn create_app_request(token: &str, params: CreateAppParams, url: &str) -> Result<App, ServerError> {
|
||||
let app = request_builder()
|
||||
.post(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.response()
|
||||
.await?;
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
pub async fn read_app_request(token: &str, params: AppIdentifier, url: &str) -> Result<Option<App>, ServerError> {
|
||||
let app = request_builder()
|
||||
.get(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.option_response()
|
||||
.await?;
|
||||
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
pub async fn update_app_request(token: &str, params: UpdateAppParams, url: &str) -> Result<(), ServerError> {
|
||||
let _ = request_builder()
|
||||
.patch(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_app_request(token: &str, params: AppIdentifier, url: &str) -> Result<(), ServerError> {
|
||||
let _ = request_builder()
|
||||
.delete(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// View
|
||||
pub async fn create_view_request(token: &str, params: CreateViewParams, url: &str) -> Result<View, ServerError> {
|
||||
let view = request_builder()
|
||||
.post(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.response()
|
||||
.await?;
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
pub async fn read_view_request(token: &str, params: ViewIdentifier, url: &str) -> Result<Option<View>, ServerError> {
|
||||
let view = request_builder()
|
||||
.get(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.option_response()
|
||||
.await?;
|
||||
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
pub async fn update_view_request(token: &str, params: UpdateViewParams, url: &str) -> Result<(), ServerError> {
|
||||
let _ = request_builder()
|
||||
.patch(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_view_request(token: &str, params: ViewIdentifiers, url: &str) -> Result<(), ServerError> {
|
||||
let _ = request_builder()
|
||||
.delete(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_trash_request(token: &str, params: TrashIdentifiers, url: &str) -> Result<(), ServerError> {
|
||||
let _ = request_builder()
|
||||
.post(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_trash_request(token: &str, params: TrashIdentifiers, url: &str) -> Result<(), ServerError> {
|
||||
let _ = request_builder()
|
||||
.delete(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.protobuf(params)?
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn read_trash_request(token: &str, url: &str) -> Result<RepeatedTrash, ServerError> {
|
||||
let repeated_trash = request_builder()
|
||||
.get(&url.to_owned())
|
||||
.header(HEADER_TOKEN, token)
|
||||
.response::<RepeatedTrash>()
|
||||
.await?;
|
||||
Ok(repeated_trash)
|
||||
}
|
38
frontend/rust-lib/dart-ffi/Cargo.toml
Normal file
38
frontend/rust-lib/dart-ffi/Cargo.toml
Normal file
@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "dart-ffi"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
name = "dart_ffi"
|
||||
# this value will change depending on the target os
|
||||
# default staticlib
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
allo-isolate = {version = "^0.1", features = ["catch-unwind",]}
|
||||
byteorder = {version = "1.3.4"}
|
||||
ffi-support = {version = "0.4.2"}
|
||||
protobuf = {version = "2.20.0"}
|
||||
lazy_static = {version = "1.4.0"}
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
|
||||
log = "0.4.14"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = {version = "1.0"}
|
||||
bytes = { version = "1.0" }
|
||||
parking_lot = "0.11"
|
||||
|
||||
lib-dispatch = {path = "../lib-dispatch" }
|
||||
flowy-sdk = {path = "../flowy-sdk"}
|
||||
flowy-derive = {path = "../flowy-derive"}
|
||||
dart-notify = {path = "../dart-notify" }
|
||||
backend-service = { path = "../backend-service" }
|
||||
|
||||
|
||||
[features]
|
||||
flutter = ["dart-notify/dart"]
|
||||
http_server = ["flowy-sdk/http_server", "flowy-sdk/use_bunyan"]
|
||||
#use_serde = ["bincode"]
|
||||
#use_protobuf= ["protobuf"]
|
2
frontend/rust-lib/dart-ffi/Flowy.toml
Normal file
2
frontend/rust-lib/dart-ffi/Flowy.toml
Normal file
@ -0,0 +1,2 @@
|
||||
proto_crates = ["src/model"]
|
||||
event_files = []
|
15
frontend/rust-lib/dart-ffi/binding.h
Normal file
15
frontend/rust-lib/dart-ffi/binding.h
Normal file
@ -0,0 +1,15 @@
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
int64_t init_sdk(char *path);
|
||||
|
||||
void async_command(int64_t port, const uint8_t *input, uintptr_t len);
|
||||
|
||||
const uint8_t *sync_command(const uint8_t *input, uintptr_t len);
|
||||
|
||||
int32_t set_stream_port(int64_t port);
|
||||
|
||||
void link_me_please(void);
|
26
frontend/rust-lib/dart-ffi/src/c.rs
Normal file
26
frontend/rust-lib/dart-ffi/src/c.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use std::mem::forget;
|
||||
|
||||
pub fn forget_rust(buf: Vec<u8>) -> *const u8 {
|
||||
let ptr = buf.as_ptr();
|
||||
forget(buf);
|
||||
ptr
|
||||
}
|
||||
|
||||
#[allow(unused_attributes)]
|
||||
#[allow(dead_code)]
|
||||
pub fn reclaim_rust(ptr: *mut u8, length: u32) {
|
||||
unsafe {
|
||||
let len: usize = length as usize;
|
||||
Vec::from_raw_parts(ptr, len, len);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extend_front_four_bytes_into_bytes(bytes: &[u8]) -> Vec<u8> {
|
||||
let mut output = Vec::with_capacity(bytes.len() + 4);
|
||||
let mut marker_bytes = [0; 4];
|
||||
BigEndian::write_u32(&mut marker_bytes, bytes.len() as u32);
|
||||
output.extend_from_slice(&marker_bytes);
|
||||
output.extend_from_slice(bytes);
|
||||
output
|
||||
}
|
96
frontend/rust-lib/dart-ffi/src/lib.rs
Normal file
96
frontend/rust-lib/dart-ffi/src/lib.rs
Normal file
@ -0,0 +1,96 @@
|
||||
mod c;
|
||||
mod model;
|
||||
mod protobuf;
|
||||
mod util;
|
||||
|
||||
use crate::{
|
||||
c::{extend_front_four_bytes_into_bytes, forget_rust},
|
||||
model::{FFIRequest, FFIResponse},
|
||||
};
|
||||
use flowy_sdk::*;
|
||||
use lazy_static::lazy_static;
|
||||
use lib_dispatch::prelude::*;
|
||||
use parking_lot::RwLock;
|
||||
use std::{ffi::CStr, os::raw::c_char, sync::Arc};
|
||||
|
||||
lazy_static! {
|
||||
static ref FLOWY_SDK: RwLock<Option<Arc<FlowySDK>>> = RwLock::new(None);
|
||||
}
|
||||
|
||||
fn dispatch() -> Arc<EventDispatch> { FLOWY_SDK.read().as_ref().unwrap().dispatch() }
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
|
||||
let c_str: &CStr = unsafe { CStr::from_ptr(path) };
|
||||
let path: &str = c_str.to_str().unwrap();
|
||||
|
||||
let server_config = ServerConfig::default();
|
||||
let config = FlowySDKConfig::new(path, server_config, "appflowy").log_filter("debug");
|
||||
*FLOWY_SDK.write() = Some(Arc::new(FlowySDK::new(config)));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn async_command(port: i64, input: *const u8, len: usize) {
|
||||
let request: ModuleRequest = FFIRequest::from_u8_pointer(input, len).into();
|
||||
log::trace!(
|
||||
"[FFI]: {} Async Event: {:?} with {} port",
|
||||
&request.id,
|
||||
&request.event,
|
||||
port
|
||||
);
|
||||
|
||||
let _ = EventDispatch::async_send_with_callback(dispatch(), request, move |resp: EventResponse| {
|
||||
log::trace!("[FFI]: Post data to dart through {} port", port);
|
||||
Box::pin(post_to_flutter(resp, port))
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sync_command(input: *const u8, len: usize) -> *const u8 {
|
||||
let request: ModuleRequest = FFIRequest::from_u8_pointer(input, len).into();
|
||||
log::trace!("[FFI]: {} Sync Event: {:?}", &request.id, &request.event,);
|
||||
let _response = EventDispatch::sync_send(dispatch(), request);
|
||||
|
||||
// FFIResponse { }
|
||||
let response_bytes = vec![];
|
||||
let result = extend_front_four_bytes_into_bytes(&response_bytes);
|
||||
forget_rust(result)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_stream_port(port: i64) -> i32 {
|
||||
dart_notify::dart::DartStreamSender::set_port(port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn link_me_please() {}
|
||||
|
||||
use backend_service::config::ServerConfig;
|
||||
use lib_dispatch::prelude::ToBytes;
|
||||
|
||||
#[inline(always)]
|
||||
async fn post_to_flutter(response: EventResponse, port: i64) {
|
||||
let isolate = allo_isolate::Isolate::new(port);
|
||||
match isolate
|
||||
.catch_unwind(async {
|
||||
let ffi_resp = FFIResponse::from(response);
|
||||
ffi_resp.into_bytes().unwrap().to_vec()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_success) => {
|
||||
log::trace!("[FFI]: Post data to dart success");
|
||||
},
|
||||
Err(e) => {
|
||||
if let Some(msg) = e.downcast_ref::<&str>() {
|
||||
log::error!("[FFI]: {:?}", msg);
|
||||
} else {
|
||||
log::error!("[FFI]: allo_isolate post panic");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
26
frontend/rust-lib/dart-ffi/src/model/ffi_request.rs
Normal file
26
frontend/rust-lib/dart-ffi/src/model/ffi_request.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use lib_dispatch::prelude::ModuleRequest;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct FFIRequest {
|
||||
#[pb(index = 1)]
|
||||
pub(crate) event: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub(crate) payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl FFIRequest {
|
||||
pub fn from_u8_pointer(pointer: *const u8, len: usize) -> Self {
|
||||
let buffer = unsafe { std::slice::from_raw_parts(pointer, len) }.to_vec();
|
||||
let bytes = Bytes::from(buffer);
|
||||
let request: FFIRequest = FFIRequest::try_from(bytes).unwrap();
|
||||
request
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::Into<ModuleRequest> for FFIRequest {
|
||||
fn into(self) -> ModuleRequest { ModuleRequest::new(self.event).payload(self.payload) }
|
||||
}
|
44
frontend/rust-lib/dart-ffi/src/model/ffi_response.rs
Normal file
44
frontend/rust-lib/dart-ffi/src/model/ffi_response.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use lib_dispatch::prelude::{EventResponse, Payload, StatusCode};
|
||||
|
||||
#[derive(ProtoBuf_Enum, Clone, Copy)]
|
||||
pub enum FFIStatusCode {
|
||||
Ok = 0,
|
||||
Err = 1,
|
||||
Internal = 2,
|
||||
}
|
||||
|
||||
impl std::default::Default for FFIStatusCode {
|
||||
fn default() -> FFIStatusCode { FFIStatusCode::Ok }
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct FFIResponse {
|
||||
#[pb(index = 1)]
|
||||
payload: Vec<u8>,
|
||||
|
||||
#[pb(index = 2)]
|
||||
code: FFIStatusCode,
|
||||
}
|
||||
|
||||
impl std::convert::From<EventResponse> for FFIResponse {
|
||||
fn from(resp: EventResponse) -> Self {
|
||||
let payload = match resp.payload {
|
||||
Payload::Bytes(bytes) => bytes.to_vec(),
|
||||
Payload::None => vec![],
|
||||
};
|
||||
|
||||
let code = match resp.status_code {
|
||||
StatusCode::Ok => FFIStatusCode::Ok,
|
||||
StatusCode::Err => FFIStatusCode::Err,
|
||||
StatusCode::Internal => FFIStatusCode::Internal,
|
||||
};
|
||||
|
||||
// let msg = match resp.error {
|
||||
// None => "".to_owned(),
|
||||
// Some(e) => format!("{:?}", e),
|
||||
// };
|
||||
|
||||
FFIResponse { payload, code }
|
||||
}
|
||||
}
|
5
frontend/rust-lib/dart-ffi/src/model/mod.rs
Normal file
5
frontend/rust-lib/dart-ffi/src/model/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod ffi_request;
|
||||
mod ffi_response;
|
||||
|
||||
pub use ffi_request::*;
|
||||
pub use ffi_response::*;
|
4
frontend/rust-lib/dart-ffi/src/protobuf/mod.rs
Normal file
4
frontend/rust-lib/dart-ffi/src/protobuf/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
mod model;
|
||||
pub use model::*;
|
||||
|
250
frontend/rust-lib/dart-ffi/src/protobuf/model/ffi_request.rs
Normal file
250
frontend/rust-lib/dart-ffi/src/protobuf/model/ffi_request.rs
Normal file
@ -0,0 +1,250 @@
|
||||
// This file is generated by rust-protobuf 2.22.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::all)]
|
||||
|
||||
#![allow(unused_attributes)]
|
||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
|
||||
#![allow(box_pointers)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(trivial_casts)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_results)]
|
||||
//! Generated file from `ffi_request.proto`
|
||||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct FFIRequest {
|
||||
// message fields
|
||||
pub event: ::std::string::String,
|
||||
pub payload: ::std::vec::Vec<u8>,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a FFIRequest {
|
||||
fn default() -> &'a FFIRequest {
|
||||
<FFIRequest as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl FFIRequest {
|
||||
pub fn new() -> FFIRequest {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
// string event = 1;
|
||||
|
||||
|
||||
pub fn get_event(&self) -> &str {
|
||||
&self.event
|
||||
}
|
||||
pub fn clear_event(&mut self) {
|
||||
self.event.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_event(&mut self, v: ::std::string::String) {
|
||||
self.event = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_event(&mut self) -> &mut ::std::string::String {
|
||||
&mut self.event
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_event(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.event, ::std::string::String::new())
|
||||
}
|
||||
|
||||
// bytes payload = 2;
|
||||
|
||||
|
||||
pub fn get_payload(&self) -> &[u8] {
|
||||
&self.payload
|
||||
}
|
||||
pub fn clear_payload(&mut self) {
|
||||
self.payload.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_payload(&mut self, v: ::std::vec::Vec<u8>) {
|
||||
self.payload = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_payload(&mut self) -> &mut ::std::vec::Vec<u8> {
|
||||
&mut self.payload
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_payload(&mut self) -> ::std::vec::Vec<u8> {
|
||||
::std::mem::replace(&mut self.payload, ::std::vec::Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for FFIRequest {
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
while !is.eof()? {
|
||||
let (field_number, wire_type) = is.read_tag_unpack()?;
|
||||
match field_number {
|
||||
1 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.event)?;
|
||||
},
|
||||
2 => {
|
||||
::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.payload)?;
|
||||
},
|
||||
_ => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u32 {
|
||||
let mut my_size = 0;
|
||||
if !self.event.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(1, &self.event);
|
||||
}
|
||||
if !self.payload.is_empty() {
|
||||
my_size += ::protobuf::rt::bytes_size(2, &self.payload);
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||
self.cached_size.set(my_size);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
if !self.event.is_empty() {
|
||||
os.write_string(1, &self.event)?;
|
||||
}
|
||||
if !self.payload.is_empty() {
|
||||
os.write_bytes(2, &self.payload)?;
|
||||
}
|
||||
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn get_cached_size(&self) -> u32 {
|
||||
self.cached_size.get()
|
||||
}
|
||||
|
||||
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
|
||||
&self.unknown_fields
|
||||
}
|
||||
|
||||
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
|
||||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
Self::descriptor_static()
|
||||
}
|
||||
|
||||
fn new() -> FFIRequest {
|
||||
FFIRequest::new()
|
||||
}
|
||||
|
||||
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||
descriptor.get(|| {
|
||||
let mut fields = ::std::vec::Vec::new();
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
|
||||
"event",
|
||||
|m: &FFIRequest| { &m.event },
|
||||
|m: &mut FFIRequest| { &mut m.event },
|
||||
));
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
|
||||
"payload",
|
||||
|m: &FFIRequest| { &m.payload },
|
||||
|m: &mut FFIRequest| { &mut m.payload },
|
||||
));
|
||||
::protobuf::reflect::MessageDescriptor::new_pb_name::<FFIRequest>(
|
||||
"FFIRequest",
|
||||
fields,
|
||||
file_descriptor_proto()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static FFIRequest {
|
||||
static instance: ::protobuf::rt::LazyV2<FFIRequest> = ::protobuf::rt::LazyV2::INIT;
|
||||
instance.get(FFIRequest::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Clear for FFIRequest {
|
||||
fn clear(&mut self) {
|
||||
self.event.clear();
|
||||
self.payload.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for FFIRequest {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for FFIRequest {
|
||||
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
|
||||
::protobuf::reflect::ReflectValueRef::Message(self)
|
||||
}
|
||||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\x11ffi_request.proto\"<\n\nFFIRequest\x12\x14\n\x05event\x18\x01\x20\
|
||||
\x01(\tR\x05event\x12\x18\n\x07payload\x18\x02\x20\x01(\x0cR\x07payloadJ\
|
||||
\x98\x01\n\x06\x12\x04\0\0\x05\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\
|
||||
\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x12\
|
||||
\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x15\n\x0c\n\x05\x04\0\x02\0\x05\
|
||||
\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\x10\n\x0c\
|
||||
\n\x05\x04\0\x02\0\x03\x12\x03\x03\x13\x14\n\x0b\n\x04\x04\0\x02\x01\x12\
|
||||
\x03\x04\x04\x16\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\t\n\x0c\n\
|
||||
\x05\x04\0\x02\x01\x01\x12\x03\x04\n\x11\n\x0c\n\x05\x04\0\x02\x01\x03\
|
||||
\x12\x03\x04\x14\x15b\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
||||
fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
|
||||
::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
|
||||
}
|
||||
|
||||
pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
|
||||
file_descriptor_proto_lazy.get(|| {
|
||||
parse_descriptor_proto()
|
||||
})
|
||||
}
|
301
frontend/rust-lib/dart-ffi/src/protobuf/model/ffi_response.rs
Normal file
301
frontend/rust-lib/dart-ffi/src/protobuf/model/ffi_response.rs
Normal file
@ -0,0 +1,301 @@
|
||||
// This file is generated by rust-protobuf 2.22.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::all)]
|
||||
|
||||
#![allow(unused_attributes)]
|
||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
|
||||
#![allow(box_pointers)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(trivial_casts)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_results)]
|
||||
//! Generated file from `ffi_response.proto`
|
||||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct FFIResponse {
|
||||
// message fields
|
||||
pub payload: ::std::vec::Vec<u8>,
|
||||
pub code: FFIStatusCode,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a FFIResponse {
|
||||
fn default() -> &'a FFIResponse {
|
||||
<FFIResponse as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl FFIResponse {
|
||||
pub fn new() -> FFIResponse {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
// bytes payload = 1;
|
||||
|
||||
|
||||
pub fn get_payload(&self) -> &[u8] {
|
||||
&self.payload
|
||||
}
|
||||
pub fn clear_payload(&mut self) {
|
||||
self.payload.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_payload(&mut self, v: ::std::vec::Vec<u8>) {
|
||||
self.payload = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_payload(&mut self) -> &mut ::std::vec::Vec<u8> {
|
||||
&mut self.payload
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_payload(&mut self) -> ::std::vec::Vec<u8> {
|
||||
::std::mem::replace(&mut self.payload, ::std::vec::Vec::new())
|
||||
}
|
||||
|
||||
// .FFIStatusCode code = 2;
|
||||
|
||||
|
||||
pub fn get_code(&self) -> FFIStatusCode {
|
||||
self.code
|
||||
}
|
||||
pub fn clear_code(&mut self) {
|
||||
self.code = FFIStatusCode::Ok;
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_code(&mut self, v: FFIStatusCode) {
|
||||
self.code = v;
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for FFIResponse {
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
while !is.eof()? {
|
||||
let (field_number, wire_type) = is.read_tag_unpack()?;
|
||||
match field_number {
|
||||
1 => {
|
||||
::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.payload)?;
|
||||
},
|
||||
2 => {
|
||||
::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.code, 2, &mut self.unknown_fields)?
|
||||
},
|
||||
_ => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u32 {
|
||||
let mut my_size = 0;
|
||||
if !self.payload.is_empty() {
|
||||
my_size += ::protobuf::rt::bytes_size(1, &self.payload);
|
||||
}
|
||||
if self.code != FFIStatusCode::Ok {
|
||||
my_size += ::protobuf::rt::enum_size(2, self.code);
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||
self.cached_size.set(my_size);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
if !self.payload.is_empty() {
|
||||
os.write_bytes(1, &self.payload)?;
|
||||
}
|
||||
if self.code != FFIStatusCode::Ok {
|
||||
os.write_enum(2, ::protobuf::ProtobufEnum::value(&self.code))?;
|
||||
}
|
||||
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn get_cached_size(&self) -> u32 {
|
||||
self.cached_size.get()
|
||||
}
|
||||
|
||||
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
|
||||
&self.unknown_fields
|
||||
}
|
||||
|
||||
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
|
||||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
Self::descriptor_static()
|
||||
}
|
||||
|
||||
fn new() -> FFIResponse {
|
||||
FFIResponse::new()
|
||||
}
|
||||
|
||||
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||
descriptor.get(|| {
|
||||
let mut fields = ::std::vec::Vec::new();
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
|
||||
"payload",
|
||||
|m: &FFIResponse| { &m.payload },
|
||||
|m: &mut FFIResponse| { &mut m.payload },
|
||||
));
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<FFIStatusCode>>(
|
||||
"code",
|
||||
|m: &FFIResponse| { &m.code },
|
||||
|m: &mut FFIResponse| { &mut m.code },
|
||||
));
|
||||
::protobuf::reflect::MessageDescriptor::new_pb_name::<FFIResponse>(
|
||||
"FFIResponse",
|
||||
fields,
|
||||
file_descriptor_proto()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static FFIResponse {
|
||||
static instance: ::protobuf::rt::LazyV2<FFIResponse> = ::protobuf::rt::LazyV2::INIT;
|
||||
instance.get(FFIResponse::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Clear for FFIResponse {
|
||||
fn clear(&mut self) {
|
||||
self.payload.clear();
|
||||
self.code = FFIStatusCode::Ok;
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for FFIResponse {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for FFIResponse {
|
||||
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
|
||||
::protobuf::reflect::ReflectValueRef::Message(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,PartialEq,Eq,Debug,Hash)]
|
||||
pub enum FFIStatusCode {
|
||||
Ok = 0,
|
||||
Err = 1,
|
||||
Internal = 2,
|
||||
}
|
||||
|
||||
impl ::protobuf::ProtobufEnum for FFIStatusCode {
|
||||
fn value(&self) -> i32 {
|
||||
*self as i32
|
||||
}
|
||||
|
||||
fn from_i32(value: i32) -> ::std::option::Option<FFIStatusCode> {
|
||||
match value {
|
||||
0 => ::std::option::Option::Some(FFIStatusCode::Ok),
|
||||
1 => ::std::option::Option::Some(FFIStatusCode::Err),
|
||||
2 => ::std::option::Option::Some(FFIStatusCode::Internal),
|
||||
_ => ::std::option::Option::None
|
||||
}
|
||||
}
|
||||
|
||||
fn values() -> &'static [Self] {
|
||||
static values: &'static [FFIStatusCode] = &[
|
||||
FFIStatusCode::Ok,
|
||||
FFIStatusCode::Err,
|
||||
FFIStatusCode::Internal,
|
||||
];
|
||||
values
|
||||
}
|
||||
|
||||
fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
|
||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||
descriptor.get(|| {
|
||||
::protobuf::reflect::EnumDescriptor::new_pb_name::<FFIStatusCode>("FFIStatusCode", file_descriptor_proto())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::marker::Copy for FFIStatusCode {
|
||||
}
|
||||
|
||||
impl ::std::default::Default for FFIStatusCode {
|
||||
fn default() -> Self {
|
||||
FFIStatusCode::Ok
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for FFIStatusCode {
|
||||
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
|
||||
::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
|
||||
}
|
||||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\x12ffi_response.proto\"K\n\x0bFFIResponse\x12\x18\n\x07payload\x18\
|
||||
\x01\x20\x01(\x0cR\x07payload\x12\"\n\x04code\x18\x02\x20\x01(\x0e2\x0e.\
|
||||
FFIStatusCodeR\x04code*.\n\rFFIStatusCode\x12\x06\n\x02Ok\x10\0\x12\x07\
|
||||
\n\x03Err\x10\x01\x12\x0c\n\x08Internal\x10\x02J\xab\x02\n\x06\x12\x04\0\
|
||||
\0\n\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\
|
||||
\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x13\n\x0b\n\x04\x04\0\x02\0\
|
||||
\x12\x03\x03\x04\x16\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\t\n\x0c\
|
||||
\n\x05\x04\0\x02\0\x01\x12\x03\x03\n\x11\n\x0c\n\x05\x04\0\x02\0\x03\x12\
|
||||
\x03\x03\x14\x15\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x1b\n\x0c\n\
|
||||
\x05\x04\0\x02\x01\x06\x12\x03\x04\x04\x11\n\x0c\n\x05\x04\0\x02\x01\x01\
|
||||
\x12\x03\x04\x12\x16\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x19\x1a\n\
|
||||
\n\n\x02\x05\0\x12\x04\x06\0\n\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\
|
||||
\x12\n\x0b\n\x04\x05\0\x02\0\x12\x03\x07\x04\x0b\n\x0c\n\x05\x05\0\x02\0\
|
||||
\x01\x12\x03\x07\x04\x06\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x07\t\n\n\
|
||||
\x0b\n\x04\x05\0\x02\x01\x12\x03\x08\x04\x0c\n\x0c\n\x05\x05\0\x02\x01\
|
||||
\x01\x12\x03\x08\x04\x07\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\n\x0b\
|
||||
\n\x0b\n\x04\x05\0\x02\x02\x12\x03\t\x04\x11\n\x0c\n\x05\x05\0\x02\x02\
|
||||
\x01\x12\x03\t\x04\x0c\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x0f\x10b\
|
||||
\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
||||
fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
|
||||
::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
|
||||
}
|
||||
|
||||
pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
|
||||
file_descriptor_proto_lazy.get(|| {
|
||||
parse_descriptor_proto()
|
||||
})
|
||||
}
|
7
frontend/rust-lib/dart-ffi/src/protobuf/model/mod.rs
Normal file
7
frontend/rust-lib/dart-ffi/src/protobuf/model/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
// Auto-generated, do not edit
|
||||
|
||||
mod ffi_response;
|
||||
pub use ffi_response::*;
|
||||
|
||||
mod ffi_request;
|
||||
pub use ffi_request::*;
|
@ -0,0 +1,6 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message FFIRequest {
|
||||
string event = 1;
|
||||
bytes payload = 2;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message FFIResponse {
|
||||
bytes payload = 1;
|
||||
FFIStatusCode code = 2;
|
||||
}
|
||||
enum FFIStatusCode {
|
||||
Ok = 0;
|
||||
Err = 1;
|
||||
Internal = 2;
|
||||
}
|
1
frontend/rust-lib/dart-ffi/src/util.rs
Normal file
1
frontend/rust-lib/dart-ffi/src/util.rs
Normal file
@ -0,0 +1 @@
|
||||
|
19
frontend/rust-lib/dart-notify/Cargo.toml
Normal file
19
frontend/rust-lib/dart-notify/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "dart-notify"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
lazy_static = {version = "1.4.0"}
|
||||
protobuf = {version = "2.20.0"}
|
||||
allo-isolate = {version = "^0.1", features = ["catch-unwind",]}
|
||||
log = "0.4.14"
|
||||
bytes = { version = "1.0" }
|
||||
|
||||
flowy-derive = {path = "../flowy-derive"}
|
||||
lib-dispatch = {path = "../lib-dispatch" }
|
||||
|
||||
[features]
|
||||
dart = []
|
3
frontend/rust-lib/dart-notify/Flowy.toml
Normal file
3
frontend/rust-lib/dart-notify/Flowy.toml
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
proto_crates = ["src/entities"]
|
||||
event_files = []
|
3
frontend/rust-lib/dart-notify/src/dart/mod.rs
Normal file
3
frontend/rust-lib/dart-notify/src/dart/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod stream_sender;
|
||||
|
||||
pub use stream_sender::*;
|
55
frontend/rust-lib/dart-notify/src/dart/stream_sender.rs
Normal file
55
frontend/rust-lib/dart-notify/src/dart/stream_sender.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use crate::entities::SubscribeObject;
|
||||
use bytes::Bytes;
|
||||
use lazy_static::lazy_static;
|
||||
use std::{convert::TryInto, sync::RwLock};
|
||||
|
||||
lazy_static! {
|
||||
static ref DART_STREAM_SENDER: RwLock<DartStreamSender> = RwLock::new(DartStreamSender::new());
|
||||
}
|
||||
|
||||
pub struct DartStreamSender {
|
||||
#[allow(dead_code)]
|
||||
isolate: Option<allo_isolate::Isolate>,
|
||||
}
|
||||
|
||||
impl DartStreamSender {
|
||||
fn new() -> Self { Self { isolate: None } }
|
||||
|
||||
fn inner_set_port(&mut self, port: i64) {
|
||||
log::info!("Setup rust to flutter stream with port {}", port);
|
||||
self.isolate = Some(allo_isolate::Isolate::new(port));
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn inner_post(&self, observable_subject: SubscribeObject) -> Result<(), String> {
|
||||
match self.isolate {
|
||||
Some(ref isolate) => {
|
||||
let bytes: Bytes = observable_subject.try_into().unwrap();
|
||||
isolate.post(bytes.to_vec());
|
||||
Ok(())
|
||||
},
|
||||
None => Err("Isolate is not set".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_port(port: i64) {
|
||||
match DART_STREAM_SENDER.write() {
|
||||
Ok(mut stream) => stream.inner_set_port(port),
|
||||
Err(e) => {
|
||||
let msg = format!("Get rust to flutter stream lock fail. {:?}", e);
|
||||
log::error!("{:?}", msg);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn post(_observable_subject: SubscribeObject) -> Result<(), String> {
|
||||
#[cfg(feature = "dart")]
|
||||
match DART_STREAM_SENDER.read() {
|
||||
Ok(stream) => stream.inner_post(_observable_subject),
|
||||
Err(e) => Err(format!("Get rust to flutter stream lock fail. {:?}", e)),
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "dart"))]
|
||||
Ok(())
|
||||
}
|
||||
}
|
3
frontend/rust-lib/dart-notify/src/entities/mod.rs
Normal file
3
frontend/rust-lib/dart-notify/src/entities/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod subject;
|
||||
|
||||
pub use subject::*;
|
47
frontend/rust-lib/dart-notify/src/entities/subject.rs
Normal file
47
frontend/rust-lib/dart-notify/src/entities/subject.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use flowy_derive::ProtoBuf;
|
||||
use std::{fmt, fmt::Formatter};
|
||||
|
||||
#[derive(Debug, Clone, ProtoBuf)]
|
||||
pub struct SubscribeObject {
|
||||
#[pb(index = 1)]
|
||||
pub source: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub ty: i32,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub id: String,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub payload: Option<Vec<u8>>,
|
||||
|
||||
#[pb(index = 5, one_of)]
|
||||
pub error: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SubscribeObject {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let _ = f.write_str(&format!("{} changed: ", &self.source))?;
|
||||
if let Some(payload) = &self.payload {
|
||||
let _ = f.write_str(&format!("send {} payload", payload.len()))?;
|
||||
}
|
||||
|
||||
if let Some(payload) = &self.error {
|
||||
let _ = f.write_str(&format!("receive {} error", payload.len()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for SubscribeObject {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
source: "".to_string(),
|
||||
ty: 0,
|
||||
id: "".to_string(),
|
||||
payload: None,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
}
|
80
frontend/rust-lib/dart-notify/src/lib.rs
Normal file
80
frontend/rust-lib/dart-notify/src/lib.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use bytes::Bytes;
|
||||
|
||||
pub mod dart;
|
||||
pub mod entities;
|
||||
mod protobuf;
|
||||
|
||||
use crate::{dart::DartStreamSender, entities::SubscribeObject};
|
||||
use lib_dispatch::prelude::ToBytes;
|
||||
|
||||
pub struct DartNotifyBuilder {
|
||||
id: String,
|
||||
payload: Option<Bytes>,
|
||||
error: Option<Bytes>,
|
||||
source: String,
|
||||
ty: i32,
|
||||
}
|
||||
|
||||
impl DartNotifyBuilder {
|
||||
pub fn new<T: Into<i32>>(id: &str, ty: T, source: &str) -> Self {
|
||||
Self {
|
||||
id: id.to_owned(),
|
||||
ty: ty.into(),
|
||||
payload: None,
|
||||
error: None,
|
||||
source: source.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn payload<T>(mut self, payload: T) -> Self
|
||||
where
|
||||
T: ToBytes,
|
||||
{
|
||||
match payload.into_bytes() {
|
||||
Ok(bytes) => self.payload = Some(bytes),
|
||||
Err(e) => {
|
||||
log::error!("Set observable payload failed: {:?}", e);
|
||||
},
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn error<T>(mut self, error: T) -> Self
|
||||
where
|
||||
T: ToBytes,
|
||||
{
|
||||
match error.into_bytes() {
|
||||
Ok(bytes) => self.error = Some(bytes),
|
||||
Err(e) => {
|
||||
log::error!("Set observable error failed: {:?}", e);
|
||||
},
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn send(self) {
|
||||
let payload = match self.payload {
|
||||
None => None,
|
||||
Some(bytes) => Some(bytes.to_vec()),
|
||||
};
|
||||
|
||||
let error = match self.error {
|
||||
None => None,
|
||||
Some(bytes) => Some(bytes.to_vec()),
|
||||
};
|
||||
|
||||
let subject = SubscribeObject {
|
||||
source: self.source,
|
||||
ty: self.ty,
|
||||
id: self.id,
|
||||
payload,
|
||||
error,
|
||||
};
|
||||
|
||||
match DartStreamSender::post(subject) {
|
||||
Ok(_) => {},
|
||||
Err(error) => log::error!("Send observable subject failed: {}", error),
|
||||
}
|
||||
}
|
||||
}
|
4
frontend/rust-lib/dart-notify/src/protobuf/mod.rs
Normal file
4
frontend/rust-lib/dart-notify/src/protobuf/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
mod model;
|
||||
pub use model::*;
|
||||
|
4
frontend/rust-lib/dart-notify/src/protobuf/model/mod.rs
Normal file
4
frontend/rust-lib/dart-notify/src/protobuf/model/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
// Auto-generated, do not edit
|
||||
|
||||
mod subject;
|
||||
pub use subject::*;
|
461
frontend/rust-lib/dart-notify/src/protobuf/model/subject.rs
Normal file
461
frontend/rust-lib/dart-notify/src/protobuf/model/subject.rs
Normal file
@ -0,0 +1,461 @@
|
||||
// This file is generated by rust-protobuf 2.22.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::all)]
|
||||
|
||||
#![allow(unused_attributes)]
|
||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
|
||||
#![allow(box_pointers)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(trivial_casts)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_results)]
|
||||
//! Generated file from `subject.proto`
|
||||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct SubscribeObject {
|
||||
// message fields
|
||||
pub source: ::std::string::String,
|
||||
pub ty: i32,
|
||||
pub id: ::std::string::String,
|
||||
// message oneof groups
|
||||
pub one_of_payload: ::std::option::Option<SubscribeObject_oneof_one_of_payload>,
|
||||
pub one_of_error: ::std::option::Option<SubscribeObject_oneof_one_of_error>,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a SubscribeObject {
|
||||
fn default() -> &'a SubscribeObject {
|
||||
<SubscribeObject as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,PartialEq,Debug)]
|
||||
pub enum SubscribeObject_oneof_one_of_payload {
|
||||
payload(::std::vec::Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Clone,PartialEq,Debug)]
|
||||
pub enum SubscribeObject_oneof_one_of_error {
|
||||
error(::std::vec::Vec<u8>),
|
||||
}
|
||||
|
||||
impl SubscribeObject {
|
||||
pub fn new() -> SubscribeObject {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
// string source = 1;
|
||||
|
||||
|
||||
pub fn get_source(&self) -> &str {
|
||||
&self.source
|
||||
}
|
||||
pub fn clear_source(&mut self) {
|
||||
self.source.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_source(&mut self, v: ::std::string::String) {
|
||||
self.source = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_source(&mut self) -> &mut ::std::string::String {
|
||||
&mut self.source
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_source(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.source, ::std::string::String::new())
|
||||
}
|
||||
|
||||
// int32 ty = 2;
|
||||
|
||||
|
||||
pub fn get_ty(&self) -> i32 {
|
||||
self.ty
|
||||
}
|
||||
pub fn clear_ty(&mut self) {
|
||||
self.ty = 0;
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_ty(&mut self, v: i32) {
|
||||
self.ty = v;
|
||||
}
|
||||
|
||||
// string id = 3;
|
||||
|
||||
|
||||
pub fn get_id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
pub fn clear_id(&mut self) {
|
||||
self.id.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_id(&mut self, v: ::std::string::String) {
|
||||
self.id = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_id(&mut self) -> &mut ::std::string::String {
|
||||
&mut self.id
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_id(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.id, ::std::string::String::new())
|
||||
}
|
||||
|
||||
// bytes payload = 4;
|
||||
|
||||
|
||||
pub fn get_payload(&self) -> &[u8] {
|
||||
match self.one_of_payload {
|
||||
::std::option::Option::Some(SubscribeObject_oneof_one_of_payload::payload(ref v)) => v,
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
pub fn clear_payload(&mut self) {
|
||||
self.one_of_payload = ::std::option::Option::None;
|
||||
}
|
||||
|
||||
pub fn has_payload(&self) -> bool {
|
||||
match self.one_of_payload {
|
||||
::std::option::Option::Some(SubscribeObject_oneof_one_of_payload::payload(..)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_payload(&mut self, v: ::std::vec::Vec<u8>) {
|
||||
self.one_of_payload = ::std::option::Option::Some(SubscribeObject_oneof_one_of_payload::payload(v))
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
pub fn mut_payload(&mut self) -> &mut ::std::vec::Vec<u8> {
|
||||
if let ::std::option::Option::Some(SubscribeObject_oneof_one_of_payload::payload(_)) = self.one_of_payload {
|
||||
} else {
|
||||
self.one_of_payload = ::std::option::Option::Some(SubscribeObject_oneof_one_of_payload::payload(::std::vec::Vec::new()));
|
||||
}
|
||||
match self.one_of_payload {
|
||||
::std::option::Option::Some(SubscribeObject_oneof_one_of_payload::payload(ref mut v)) => v,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_payload(&mut self) -> ::std::vec::Vec<u8> {
|
||||
if self.has_payload() {
|
||||
match self.one_of_payload.take() {
|
||||
::std::option::Option::Some(SubscribeObject_oneof_one_of_payload::payload(v)) => v,
|
||||
_ => panic!(),
|
||||
}
|
||||
} else {
|
||||
::std::vec::Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
// bytes error = 5;
|
||||
|
||||
|
||||
pub fn get_error(&self) -> &[u8] {
|
||||
match self.one_of_error {
|
||||
::std::option::Option::Some(SubscribeObject_oneof_one_of_error::error(ref v)) => v,
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
pub fn clear_error(&mut self) {
|
||||
self.one_of_error = ::std::option::Option::None;
|
||||
}
|
||||
|
||||
pub fn has_error(&self) -> bool {
|
||||
match self.one_of_error {
|
||||
::std::option::Option::Some(SubscribeObject_oneof_one_of_error::error(..)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_error(&mut self, v: ::std::vec::Vec<u8>) {
|
||||
self.one_of_error = ::std::option::Option::Some(SubscribeObject_oneof_one_of_error::error(v))
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
pub fn mut_error(&mut self) -> &mut ::std::vec::Vec<u8> {
|
||||
if let ::std::option::Option::Some(SubscribeObject_oneof_one_of_error::error(_)) = self.one_of_error {
|
||||
} else {
|
||||
self.one_of_error = ::std::option::Option::Some(SubscribeObject_oneof_one_of_error::error(::std::vec::Vec::new()));
|
||||
}
|
||||
match self.one_of_error {
|
||||
::std::option::Option::Some(SubscribeObject_oneof_one_of_error::error(ref mut v)) => v,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_error(&mut self) -> ::std::vec::Vec<u8> {
|
||||
if self.has_error() {
|
||||
match self.one_of_error.take() {
|
||||
::std::option::Option::Some(SubscribeObject_oneof_one_of_error::error(v)) => v,
|
||||
_ => panic!(),
|
||||
}
|
||||
} else {
|
||||
::std::vec::Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for SubscribeObject {
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
while !is.eof()? {
|
||||
let (field_number, wire_type) = is.read_tag_unpack()?;
|
||||
match field_number {
|
||||
1 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.source)?;
|
||||
},
|
||||
2 => {
|
||||
if wire_type != ::protobuf::wire_format::WireTypeVarint {
|
||||
return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
|
||||
}
|
||||
let tmp = is.read_int32()?;
|
||||
self.ty = tmp;
|
||||
},
|
||||
3 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
|
||||
},
|
||||
4 => {
|
||||
if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
|
||||
return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
|
||||
}
|
||||
self.one_of_payload = ::std::option::Option::Some(SubscribeObject_oneof_one_of_payload::payload(is.read_bytes()?));
|
||||
},
|
||||
5 => {
|
||||
if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
|
||||
return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
|
||||
}
|
||||
self.one_of_error = ::std::option::Option::Some(SubscribeObject_oneof_one_of_error::error(is.read_bytes()?));
|
||||
},
|
||||
_ => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u32 {
|
||||
let mut my_size = 0;
|
||||
if !self.source.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(1, &self.source);
|
||||
}
|
||||
if self.ty != 0 {
|
||||
my_size += ::protobuf::rt::value_size(2, self.ty, ::protobuf::wire_format::WireTypeVarint);
|
||||
}
|
||||
if !self.id.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(3, &self.id);
|
||||
}
|
||||
if let ::std::option::Option::Some(ref v) = self.one_of_payload {
|
||||
match v {
|
||||
&SubscribeObject_oneof_one_of_payload::payload(ref v) => {
|
||||
my_size += ::protobuf::rt::bytes_size(4, &v);
|
||||
},
|
||||
};
|
||||
}
|
||||
if let ::std::option::Option::Some(ref v) = self.one_of_error {
|
||||
match v {
|
||||
&SubscribeObject_oneof_one_of_error::error(ref v) => {
|
||||
my_size += ::protobuf::rt::bytes_size(5, &v);
|
||||
},
|
||||
};
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||
self.cached_size.set(my_size);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
if !self.source.is_empty() {
|
||||
os.write_string(1, &self.source)?;
|
||||
}
|
||||
if self.ty != 0 {
|
||||
os.write_int32(2, self.ty)?;
|
||||
}
|
||||
if !self.id.is_empty() {
|
||||
os.write_string(3, &self.id)?;
|
||||
}
|
||||
if let ::std::option::Option::Some(ref v) = self.one_of_payload {
|
||||
match v {
|
||||
&SubscribeObject_oneof_one_of_payload::payload(ref v) => {
|
||||
os.write_bytes(4, v)?;
|
||||
},
|
||||
};
|
||||
}
|
||||
if let ::std::option::Option::Some(ref v) = self.one_of_error {
|
||||
match v {
|
||||
&SubscribeObject_oneof_one_of_error::error(ref v) => {
|
||||
os.write_bytes(5, v)?;
|
||||
},
|
||||
};
|
||||
}
|
||||
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn get_cached_size(&self) -> u32 {
|
||||
self.cached_size.get()
|
||||
}
|
||||
|
||||
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
|
||||
&self.unknown_fields
|
||||
}
|
||||
|
||||
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
|
||||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
Self::descriptor_static()
|
||||
}
|
||||
|
||||
fn new() -> SubscribeObject {
|
||||
SubscribeObject::new()
|
||||
}
|
||||
|
||||
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||
descriptor.get(|| {
|
||||
let mut fields = ::std::vec::Vec::new();
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
|
||||
"source",
|
||||
|m: &SubscribeObject| { &m.source },
|
||||
|m: &mut SubscribeObject| { &mut m.source },
|
||||
));
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt32>(
|
||||
"ty",
|
||||
|m: &SubscribeObject| { &m.ty },
|
||||
|m: &mut SubscribeObject| { &mut m.ty },
|
||||
));
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
|
||||
"id",
|
||||
|m: &SubscribeObject| { &m.id },
|
||||
|m: &mut SubscribeObject| { &mut m.id },
|
||||
));
|
||||
fields.push(::protobuf::reflect::accessor::make_singular_bytes_accessor::<_>(
|
||||
"payload",
|
||||
SubscribeObject::has_payload,
|
||||
SubscribeObject::get_payload,
|
||||
));
|
||||
fields.push(::protobuf::reflect::accessor::make_singular_bytes_accessor::<_>(
|
||||
"error",
|
||||
SubscribeObject::has_error,
|
||||
SubscribeObject::get_error,
|
||||
));
|
||||
::protobuf::reflect::MessageDescriptor::new_pb_name::<SubscribeObject>(
|
||||
"SubscribeObject",
|
||||
fields,
|
||||
file_descriptor_proto()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static SubscribeObject {
|
||||
static instance: ::protobuf::rt::LazyV2<SubscribeObject> = ::protobuf::rt::LazyV2::INIT;
|
||||
instance.get(SubscribeObject::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Clear for SubscribeObject {
|
||||
fn clear(&mut self) {
|
||||
self.source.clear();
|
||||
self.ty = 0;
|
||||
self.id.clear();
|
||||
self.one_of_payload = ::std::option::Option::None;
|
||||
self.one_of_error = ::std::option::Option::None;
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for SubscribeObject {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for SubscribeObject {
|
||||
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
|
||||
::protobuf::reflect::ReflectValueRef::Message(self)
|
||||
}
|
||||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\rsubject.proto\"\x9f\x01\n\x0fSubscribeObject\x12\x16\n\x06source\x18\
|
||||
\x01\x20\x01(\tR\x06source\x12\x0e\n\x02ty\x18\x02\x20\x01(\x05R\x02ty\
|
||||
\x12\x0e\n\x02id\x18\x03\x20\x01(\tR\x02id\x12\x1a\n\x07payload\x18\x04\
|
||||
\x20\x01(\x0cH\0R\x07payload\x12\x16\n\x05error\x18\x05\x20\x01(\x0cH\
|
||||
\x01R\x05errorB\x10\n\x0eone_of_payloadB\x0e\n\x0cone_of_errorJ\xf3\x02\
|
||||
\n\x06\x12\x04\0\0\x08\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\
|
||||
\0\x12\x04\x02\0\x08\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x17\n\x0b\n\
|
||||
\x04\x04\0\x02\0\x12\x03\x03\x04\x16\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\
|
||||
\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\x11\n\x0c\n\x05\
|
||||
\x04\0\x02\0\x03\x12\x03\x03\x14\x15\n\x0b\n\x04\x04\0\x02\x01\x12\x03\
|
||||
\x04\x04\x11\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\t\n\x0c\n\x05\
|
||||
\x04\0\x02\x01\x01\x12\x03\x04\n\x0c\n\x0c\n\x05\x04\0\x02\x01\x03\x12\
|
||||
\x03\x04\x0f\x10\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x12\n\x0c\n\
|
||||
\x05\x04\0\x02\x02\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\
|
||||
\x12\x03\x05\x0b\r\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x10\x11\n\
|
||||
\x0b\n\x04\x04\0\x08\0\x12\x03\x06\x04/\n\x0c\n\x05\x04\0\x08\0\x01\x12\
|
||||
\x03\x06\n\x18\n\x0b\n\x04\x04\0\x02\x03\x12\x03\x06\x1b-\n\x0c\n\x05\
|
||||
\x04\0\x02\x03\x05\x12\x03\x06\x1b\x20\n\x0c\n\x05\x04\0\x02\x03\x01\x12\
|
||||
\x03\x06!(\n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x06+,\n\x0b\n\x04\x04\0\
|
||||
\x08\x01\x12\x03\x07\x04+\n\x0c\n\x05\x04\0\x08\x01\x01\x12\x03\x07\n\
|
||||
\x16\n\x0b\n\x04\x04\0\x02\x04\x12\x03\x07\x19)\n\x0c\n\x05\x04\0\x02\
|
||||
\x04\x05\x12\x03\x07\x19\x1e\n\x0c\n\x05\x04\0\x02\x04\x01\x12\x03\x07\
|
||||
\x1f$\n\x0c\n\x05\x04\0\x02\x04\x03\x12\x03\x07'(b\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
||||
fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
|
||||
::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
|
||||
}
|
||||
|
||||
pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
|
||||
file_descriptor_proto_lazy.get(|| {
|
||||
parse_descriptor_proto()
|
||||
})
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message SubscribeObject {
|
||||
string source = 1;
|
||||
int32 ty = 2;
|
||||
string id = 3;
|
||||
oneof one_of_payload { bytes payload = 4; };
|
||||
oneof one_of_error { bytes error = 5; };
|
||||
}
|
11
frontend/rust-lib/flowy-ast/Cargo.toml
Normal file
11
frontend/rust-lib/flowy-ast/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "flowy-ast"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1.0.60", features = ["extra-traits", "parsing", "derive", "full"]}
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
232
frontend/rust-lib/flowy-ast/src/ast.rs
Normal file
232
frontend/rust-lib/flowy-ast/src/ast.rs
Normal file
@ -0,0 +1,232 @@
|
||||
use crate::{attr, ty_ext::*, AttrsContainer, Ctxt};
|
||||
use syn::{self, punctuated::Punctuated};
|
||||
|
||||
pub struct ASTContainer<'a> {
|
||||
/// The struct or enum name (without generics).
|
||||
pub ident: syn::Ident,
|
||||
/// Attributes on the structure.
|
||||
pub attrs: AttrsContainer,
|
||||
/// The contents of the struct or enum.
|
||||
pub data: ASTData<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ASTContainer<'a> {
|
||||
pub fn from_ast(cx: &Ctxt, ast: &'a syn::DeriveInput) -> Option<ASTContainer<'a>> {
|
||||
let attrs = AttrsContainer::from_ast(cx, ast);
|
||||
// syn::DeriveInput
|
||||
// 1. syn::DataUnion
|
||||
// 2. syn::DataStruct
|
||||
// 3. syn::DataEnum
|
||||
let data = match &ast.data {
|
||||
syn::Data::Struct(data) => {
|
||||
// https://docs.rs/syn/1.0.48/syn/struct.DataStruct.html
|
||||
let (style, fields) = struct_from_ast(cx, &data.fields);
|
||||
ASTData::Struct(style, fields)
|
||||
},
|
||||
syn::Data::Union(_) => {
|
||||
cx.error_spanned_by(ast, "Does not support derive for unions");
|
||||
return None;
|
||||
},
|
||||
syn::Data::Enum(data) => {
|
||||
// https://docs.rs/syn/1.0.48/syn/struct.DataEnum.html
|
||||
ASTData::Enum(enum_from_ast(cx, &ast.ident, &data.variants, &ast.attrs))
|
||||
},
|
||||
};
|
||||
|
||||
let ident = ast.ident.clone();
|
||||
let item = ASTContainer { ident, attrs, data };
|
||||
Some(item)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ASTData<'a> {
|
||||
Struct(ASTStyle, Vec<ASTField<'a>>),
|
||||
Enum(Vec<ASTEnumVariant<'a>>),
|
||||
}
|
||||
|
||||
impl<'a> ASTData<'a> {
|
||||
pub fn all_fields(&'a self) -> Box<dyn Iterator<Item = &'a ASTField<'a>> + 'a> {
|
||||
match self {
|
||||
ASTData::Enum(variants) => Box::new(variants.iter().flat_map(|variant| variant.fields.iter())),
|
||||
ASTData::Struct(_, fields) => Box::new(fields.iter()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_variants(&'a self) -> Box<dyn Iterator<Item = &'a attr::ASTEnumAttrVariant> + 'a> {
|
||||
match self {
|
||||
ASTData::Enum(variants) => {
|
||||
let iter = variants.iter().map(|variant| &variant.attrs);
|
||||
Box::new(iter)
|
||||
},
|
||||
ASTData::Struct(_, fields) => {
|
||||
let iter = fields.iter().flat_map(|_| None);
|
||||
Box::new(iter)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_idents(&'a self) -> Box<dyn Iterator<Item = &'a syn::Ident> + 'a> {
|
||||
match self {
|
||||
ASTData::Enum(variants) => Box::new(variants.iter().map(|v| &v.ident)),
|
||||
ASTData::Struct(_, fields) => {
|
||||
let iter = fields.iter().flat_map(|f| match &f.member {
|
||||
syn::Member::Named(ident) => Some(ident),
|
||||
_ => None,
|
||||
});
|
||||
Box::new(iter)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A variant of an enum.
|
||||
pub struct ASTEnumVariant<'a> {
|
||||
pub ident: syn::Ident,
|
||||
pub attrs: attr::ASTEnumAttrVariant,
|
||||
pub style: ASTStyle,
|
||||
pub fields: Vec<ASTField<'a>>,
|
||||
pub original: &'a syn::Variant,
|
||||
}
|
||||
|
||||
impl<'a> ASTEnumVariant<'a> {
|
||||
pub fn name(&self) -> String { self.ident.to_string() }
|
||||
}
|
||||
|
||||
pub enum BracketCategory {
|
||||
Other,
|
||||
Opt,
|
||||
Vec,
|
||||
Map((String, String)),
|
||||
}
|
||||
|
||||
pub struct ASTField<'a> {
|
||||
pub member: syn::Member,
|
||||
pub attrs: attr::ASTAttrField,
|
||||
pub ty: &'a syn::Type,
|
||||
pub original: &'a syn::Field,
|
||||
pub bracket_ty: Option<syn::Ident>,
|
||||
pub bracket_inner_ty: Option<syn::Ident>,
|
||||
pub bracket_category: Option<BracketCategory>,
|
||||
}
|
||||
|
||||
impl<'a> ASTField<'a> {
|
||||
pub fn new(cx: &Ctxt, field: &'a syn::Field, index: usize) -> Self {
|
||||
let mut bracket_inner_ty = None;
|
||||
let mut bracket_ty = None;
|
||||
let mut bracket_category = Some(BracketCategory::Other);
|
||||
match parse_ty(cx, &field.ty) {
|
||||
Some(inner) => {
|
||||
match inner.primitive_ty {
|
||||
PrimitiveTy::Map(map_info) => {
|
||||
bracket_category = Some(BracketCategory::Map((map_info.key.clone(), map_info.value.clone())))
|
||||
},
|
||||
PrimitiveTy::Vec => {
|
||||
bracket_category = Some(BracketCategory::Vec);
|
||||
},
|
||||
PrimitiveTy::Opt => {
|
||||
bracket_category = Some(BracketCategory::Opt);
|
||||
},
|
||||
PrimitiveTy::Other => {
|
||||
bracket_category = Some(BracketCategory::Other);
|
||||
},
|
||||
}
|
||||
|
||||
match *inner.bracket_ty_info {
|
||||
Some(bracketed_inner_ty) => {
|
||||
bracket_inner_ty = Some(bracketed_inner_ty.ident.clone());
|
||||
bracket_ty = Some(inner.ident.clone());
|
||||
},
|
||||
None => {
|
||||
bracket_ty = Some(inner.ident.clone());
|
||||
},
|
||||
}
|
||||
},
|
||||
None => {
|
||||
cx.error_spanned_by(&field.ty, "fail to get the ty inner type");
|
||||
},
|
||||
}
|
||||
|
||||
ASTField {
|
||||
member: match &field.ident {
|
||||
Some(ident) => syn::Member::Named(ident.clone()),
|
||||
None => syn::Member::Unnamed(index.into()),
|
||||
},
|
||||
attrs: attr::ASTAttrField::from_ast(cx, index, field),
|
||||
ty: &field.ty,
|
||||
original: field,
|
||||
bracket_ty,
|
||||
bracket_inner_ty,
|
||||
bracket_category,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ty_as_str(&self) -> String {
|
||||
match self.bracket_inner_ty {
|
||||
Some(ref ty) => ty.to_string(),
|
||||
None => self.bracket_ty.as_ref().unwrap().clone().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn name(&self) -> Option<syn::Ident> {
|
||||
if let syn::Member::Named(ident) = &self.member {
|
||||
return Some(ident.clone());
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_option(&self) -> bool { attr::is_option(&self.ty) }
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ASTStyle {
|
||||
Struct,
|
||||
/// Many unnamed fields.
|
||||
Tuple,
|
||||
/// One unnamed field.
|
||||
NewType,
|
||||
/// No fields.
|
||||
Unit,
|
||||
}
|
||||
|
||||
pub fn struct_from_ast<'a>(cx: &Ctxt, fields: &'a syn::Fields) -> (ASTStyle, Vec<ASTField<'a>>) {
|
||||
match fields {
|
||||
syn::Fields::Named(fields) => (ASTStyle::Struct, fields_from_ast(cx, &fields.named)),
|
||||
syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
|
||||
(ASTStyle::NewType, fields_from_ast(cx, &fields.unnamed))
|
||||
},
|
||||
syn::Fields::Unnamed(fields) => (ASTStyle::Tuple, fields_from_ast(cx, &fields.unnamed)),
|
||||
syn::Fields::Unit => (ASTStyle::Unit, Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enum_from_ast<'a>(
|
||||
cx: &Ctxt,
|
||||
ident: &syn::Ident,
|
||||
variants: &'a Punctuated<syn::Variant, Token![,]>,
|
||||
enum_attrs: &Vec<syn::Attribute>,
|
||||
) -> Vec<ASTEnumVariant<'a>> {
|
||||
variants
|
||||
.iter()
|
||||
.flat_map(|variant| {
|
||||
let attrs = attr::ASTEnumAttrVariant::from_ast(cx, ident, variant, enum_attrs);
|
||||
let (style, fields) = struct_from_ast(cx, &variant.fields);
|
||||
Some(ASTEnumVariant {
|
||||
ident: variant.ident.clone(),
|
||||
attrs,
|
||||
style,
|
||||
fields,
|
||||
original: variant,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn fields_from_ast<'a>(cx: &Ctxt, fields: &'a Punctuated<syn::Field, Token![,]>) -> Vec<ASTField<'a>> {
|
||||
fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, field)| ASTField::new(cx, field, index))
|
||||
.collect()
|
||||
}
|
491
frontend/rust-lib/flowy-ast/src/attr.rs
Normal file
491
frontend/rust-lib/flowy-ast/src/attr.rs
Normal file
@ -0,0 +1,491 @@
|
||||
use crate::{symbol::*, Ctxt};
|
||||
|
||||
use quote::ToTokens;
|
||||
use syn::{
|
||||
self,
|
||||
parse::{self, Parse},
|
||||
Meta::{List, NameValue, Path},
|
||||
NestedMeta::{Lit, Meta},
|
||||
};
|
||||
|
||||
use proc_macro2::{Group, Span, TokenStream, TokenTree};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct AttrsContainer {
|
||||
name: String,
|
||||
pb_struct_type: Option<syn::Type>,
|
||||
pb_enum_type: Option<syn::Type>,
|
||||
}
|
||||
|
||||
impl AttrsContainer {
|
||||
/// Extract out the `#[pb(...)]` attributes from an item.
|
||||
pub fn from_ast(cx: &Ctxt, item: &syn::DeriveInput) -> Self {
|
||||
let mut pb_struct_type = ASTAttr::none(cx, PB_STRUCT);
|
||||
let mut pb_enum_type = ASTAttr::none(cx, PB_ENUM);
|
||||
for meta_item in item.attrs.iter().flat_map(|attr| get_meta_items(cx, attr)).flatten() {
|
||||
match &meta_item {
|
||||
// Parse `#[pb(struct = "Type")]
|
||||
Meta(NameValue(m)) if m.path == PB_STRUCT => {
|
||||
if let Ok(into_ty) = parse_lit_into_ty(cx, PB_STRUCT, &m.lit) {
|
||||
pb_struct_type.set_opt(&m.path, Some(into_ty));
|
||||
}
|
||||
},
|
||||
|
||||
// Parse `#[pb(enum = "Type")]
|
||||
Meta(NameValue(m)) if m.path == PB_ENUM => {
|
||||
if let Ok(into_ty) = parse_lit_into_ty(cx, PB_ENUM, &m.lit) {
|
||||
pb_enum_type.set_opt(&m.path, Some(into_ty));
|
||||
}
|
||||
},
|
||||
|
||||
Meta(meta_item) => {
|
||||
let path = meta_item.path().into_token_stream().to_string().replace(' ', "");
|
||||
cx.error_spanned_by(meta_item.path(), format!("unknown pb container attribute `{}`", path));
|
||||
},
|
||||
|
||||
Lit(lit) => {
|
||||
cx.error_spanned_by(lit, "unexpected literal in pb container attribute");
|
||||
},
|
||||
}
|
||||
}
|
||||
match &item.data {
|
||||
syn::Data::Struct(_) => {
|
||||
pb_struct_type.set_if_none(default_pb_type(&cx, &item.ident));
|
||||
},
|
||||
syn::Data::Enum(_) => {
|
||||
pb_enum_type.set_if_none(default_pb_type(&cx, &item.ident));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
AttrsContainer {
|
||||
name: item.ident.to_string(),
|
||||
pb_struct_type: pb_struct_type.get(),
|
||||
pb_enum_type: pb_enum_type.get(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pb_struct_type(&self) -> Option<&syn::Type> { self.pb_struct_type.as_ref() }
|
||||
|
||||
pub fn pb_enum_type(&self) -> Option<&syn::Type> { self.pb_enum_type.as_ref() }
|
||||
}
|
||||
|
||||
struct ASTAttr<'c, T> {
|
||||
cx: &'c Ctxt,
|
||||
name: Symbol,
|
||||
tokens: TokenStream,
|
||||
value: Option<T>,
|
||||
}
|
||||
|
||||
impl<'c, T> ASTAttr<'c, T> {
|
||||
fn none(cx: &'c Ctxt, name: Symbol) -> Self {
|
||||
ASTAttr {
|
||||
cx,
|
||||
name,
|
||||
tokens: TokenStream::new(),
|
||||
value: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set<A: ToTokens>(&mut self, obj: A, value: T) {
|
||||
let tokens = obj.into_token_stream();
|
||||
|
||||
if self.value.is_some() {
|
||||
self.cx
|
||||
.error_spanned_by(tokens, format!("duplicate attribute `{}`", self.name));
|
||||
} else {
|
||||
self.tokens = tokens;
|
||||
self.value = Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_opt<A: ToTokens>(&mut self, obj: A, value: Option<T>) {
|
||||
if let Some(value) = value {
|
||||
self.set(obj, value);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_if_none(&mut self, value: T) {
|
||||
if self.value.is_none() {
|
||||
self.value = Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn get(self) -> Option<T> { self.value }
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn get_with_tokens(self) -> Option<(TokenStream, T)> {
|
||||
match self.value {
|
||||
Some(v) => Some((self.tokens, v)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ASTAttrField {
|
||||
#[allow(dead_code)]
|
||||
name: String,
|
||||
pb_index: Option<syn::LitInt>,
|
||||
pb_one_of: bool,
|
||||
skip_serializing: bool,
|
||||
skip_deserializing: bool,
|
||||
serialize_with: Option<syn::ExprPath>,
|
||||
deserialize_with: Option<syn::ExprPath>,
|
||||
}
|
||||
|
||||
impl ASTAttrField {
|
||||
/// Extract out the `#[pb(...)]` attributes from a struct field.
|
||||
pub fn from_ast(cx: &Ctxt, index: usize, field: &syn::Field) -> Self {
|
||||
let mut pb_index = ASTAttr::none(cx, PB_INDEX);
|
||||
let mut pb_one_of = BoolAttr::none(cx, PB_ONE_OF);
|
||||
let mut serialize_with = ASTAttr::none(cx, SERIALIZE_WITH);
|
||||
let mut skip_serializing = BoolAttr::none(cx, SKIP_SERIALIZING);
|
||||
let mut deserialize_with = ASTAttr::none(cx, DESERIALIZE_WITH);
|
||||
let mut skip_deserializing = BoolAttr::none(cx, SKIP_DESERIALIZING);
|
||||
|
||||
let ident = match &field.ident {
|
||||
Some(ident) => ident.to_string(),
|
||||
None => index.to_string(),
|
||||
};
|
||||
|
||||
for meta_item in field.attrs.iter().flat_map(|attr| get_meta_items(cx, attr)).flatten() {
|
||||
match &meta_item {
|
||||
// Parse `#[pb(skip)]`
|
||||
Meta(Path(word)) if word == SKIP => {
|
||||
skip_serializing.set_true(word);
|
||||
skip_deserializing.set_true(word);
|
||||
},
|
||||
|
||||
// Parse '#[pb(index = x)]'
|
||||
Meta(NameValue(m)) if m.path == PB_INDEX => {
|
||||
if let syn::Lit::Int(lit) = &m.lit {
|
||||
pb_index.set(&m.path, lit.clone());
|
||||
}
|
||||
},
|
||||
|
||||
// Parse `#[pb(one_of)]`
|
||||
Meta(Path(path)) if path == PB_ONE_OF => {
|
||||
pb_one_of.set_true(path);
|
||||
},
|
||||
|
||||
// Parse `#[pb(serialize_with = "...")]`
|
||||
Meta(NameValue(m)) if m.path == SERIALIZE_WITH => {
|
||||
if let Ok(path) = parse_lit_into_expr_path(cx, SERIALIZE_WITH, &m.lit) {
|
||||
serialize_with.set(&m.path, path);
|
||||
}
|
||||
},
|
||||
|
||||
// Parse `#[pb(deserialize_with = "...")]`
|
||||
Meta(NameValue(m)) if m.path == DESERIALIZE_WITH => {
|
||||
if let Ok(path) = parse_lit_into_expr_path(cx, DESERIALIZE_WITH, &m.lit) {
|
||||
deserialize_with.set(&m.path, path);
|
||||
}
|
||||
},
|
||||
|
||||
Meta(meta_item) => {
|
||||
let path = meta_item.path().into_token_stream().to_string().replace(' ', "");
|
||||
cx.error_spanned_by(meta_item.path(), format!("unknown field attribute `{}`", path));
|
||||
},
|
||||
|
||||
Lit(lit) => {
|
||||
cx.error_spanned_by(lit, "unexpected literal in pb field attribute");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
ASTAttrField {
|
||||
name: ident.to_string().clone(),
|
||||
pb_index: pb_index.get(),
|
||||
pb_one_of: pb_one_of.get(),
|
||||
skip_serializing: skip_serializing.get(),
|
||||
skip_deserializing: skip_deserializing.get(),
|
||||
serialize_with: serialize_with.get(),
|
||||
deserialize_with: deserialize_with.get(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn pb_index(&self) -> Option<String> {
|
||||
match self.pb_index {
|
||||
Some(ref lit) => Some(lit.base10_digits().to_string()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_one_of(&self) -> bool { self.pb_one_of }
|
||||
|
||||
pub fn serialize_with(&self) -> Option<&syn::ExprPath> { self.serialize_with.as_ref() }
|
||||
|
||||
pub fn deserialize_with(&self) -> Option<&syn::ExprPath> { self.deserialize_with.as_ref() }
|
||||
|
||||
pub fn skip_serializing(&self) -> bool { self.skip_serializing }
|
||||
|
||||
pub fn skip_deserializing(&self) -> bool { self.skip_deserializing }
|
||||
}
|
||||
|
||||
pub enum Default {
|
||||
/// Field must always be specified because it does not have a default.
|
||||
None,
|
||||
/// The default is given by `std::default::Default::default()`.
|
||||
Default,
|
||||
/// The default is given by this function.
|
||||
Path(syn::ExprPath),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventAttrs {
|
||||
input: Option<syn::Path>,
|
||||
output: Option<syn::Path>,
|
||||
error_ty: Option<String>,
|
||||
pub ignore: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ASTEnumAttrVariant {
|
||||
pub enum_name: String,
|
||||
pub enum_item_name: String,
|
||||
pub value: String,
|
||||
pub event_attrs: EventAttrs,
|
||||
}
|
||||
|
||||
impl ASTEnumAttrVariant {
|
||||
pub fn from_ast(ctxt: &Ctxt, ident: &syn::Ident, variant: &syn::Variant, enum_attrs: &Vec<syn::Attribute>) -> Self {
|
||||
let enum_item_name = variant.ident.to_string();
|
||||
let enum_name = ident.to_string();
|
||||
let mut value = String::new();
|
||||
if variant.discriminant.is_some() {
|
||||
match variant.discriminant.as_ref().unwrap().1 {
|
||||
syn::Expr::Lit(ref expr_list) => {
|
||||
let lit_int = if let syn::Lit::Int(ref int_value) = expr_list.lit {
|
||||
int_value
|
||||
} else {
|
||||
unimplemented!()
|
||||
};
|
||||
value = lit_int.base10_digits().to_string();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
let event_attrs = get_event_attrs_from(ctxt, &variant.attrs, enum_attrs);
|
||||
ASTEnumAttrVariant {
|
||||
enum_name,
|
||||
enum_item_name,
|
||||
value,
|
||||
event_attrs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_input(&self) -> Option<syn::Path> { self.event_attrs.input.clone() }
|
||||
|
||||
pub fn event_output(&self) -> Option<syn::Path> { self.event_attrs.output.clone() }
|
||||
|
||||
pub fn event_error(&self) -> String { self.event_attrs.error_ty.as_ref().unwrap().clone() }
|
||||
}
|
||||
|
||||
fn get_event_attrs_from(
|
||||
ctxt: &Ctxt,
|
||||
variant_attrs: &Vec<syn::Attribute>,
|
||||
enum_attrs: &Vec<syn::Attribute>,
|
||||
) -> EventAttrs {
|
||||
let mut event_attrs = EventAttrs {
|
||||
input: None,
|
||||
output: None,
|
||||
error_ty: None,
|
||||
ignore: false,
|
||||
};
|
||||
|
||||
enum_attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path.segments.iter().find(|s| s.ident == EVENT_ERR).is_some())
|
||||
.for_each(|attr| {
|
||||
if let Ok(NameValue(named_value)) = attr.parse_meta() {
|
||||
if let syn::Lit::Str(s) = named_value.lit {
|
||||
event_attrs.error_ty = Some(s.value());
|
||||
} else {
|
||||
eprintln!("❌ {} should not be empty", EVENT_ERR);
|
||||
}
|
||||
} else {
|
||||
eprintln!("❌ Can not find any {} on attr: {:#?}", EVENT_ERR, attr);
|
||||
}
|
||||
});
|
||||
|
||||
let mut extract_event_attr = |attr: &syn::Attribute, meta_item: &syn::NestedMeta| match &meta_item {
|
||||
Meta(NameValue(name_value)) => {
|
||||
if name_value.path == EVENT_INPUT {
|
||||
if let syn::Lit::Str(s) = &name_value.lit {
|
||||
let input_type = parse_lit_str(s)
|
||||
.map_err(|_| {
|
||||
ctxt.error_spanned_by(s, format!("failed to parse request deserializer {:?}", s.value()))
|
||||
})
|
||||
.unwrap();
|
||||
event_attrs.input = Some(input_type);
|
||||
}
|
||||
}
|
||||
|
||||
if name_value.path == EVENT_OUTPUT {
|
||||
if let syn::Lit::Str(s) = &name_value.lit {
|
||||
let output_type = parse_lit_str(s)
|
||||
.map_err(|_| {
|
||||
ctxt.error_spanned_by(s, format!("failed to parse response deserializer {:?}", s.value()))
|
||||
})
|
||||
.unwrap();
|
||||
event_attrs.output = Some(output_type);
|
||||
}
|
||||
}
|
||||
},
|
||||
Meta(Path(word)) => {
|
||||
if word == EVENT_IGNORE && attr.path == EVENT {
|
||||
event_attrs.ignore = true;
|
||||
}
|
||||
},
|
||||
Lit(s) => ctxt.error_spanned_by(s, "unexpected attribute"),
|
||||
_ => ctxt.error_spanned_by(meta_item, "unexpected attribute"),
|
||||
};
|
||||
|
||||
let attr_meta_items_info = variant_attrs
|
||||
.iter()
|
||||
.flat_map(|attr| match get_meta_items(ctxt, attr) {
|
||||
Ok(items) => Some((attr, items)),
|
||||
Err(_) => None,
|
||||
})
|
||||
.collect::<Vec<(&syn::Attribute, Vec<syn::NestedMeta>)>>();
|
||||
|
||||
for (attr, nested_metas) in attr_meta_items_info {
|
||||
nested_metas
|
||||
.iter()
|
||||
.for_each(|meta_item| extract_event_attr(attr, meta_item))
|
||||
}
|
||||
|
||||
// eprintln!("😁{:#?}", event_attrs);
|
||||
|
||||
event_attrs
|
||||
}
|
||||
|
||||
pub fn get_meta_items(cx: &Ctxt, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {
|
||||
if attr.path != PB_ATTRS && attr.path != EVENT {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
// http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html
|
||||
match attr.parse_meta() {
|
||||
Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
|
||||
Ok(other) => {
|
||||
cx.error_spanned_by(other, "expected #[pb(...)] or or #[event(...)]");
|
||||
Err(())
|
||||
},
|
||||
Err(err) => {
|
||||
cx.error_spanned_by(attr, "attribute must be str, e.g. #[pb(xx = \"xxx\")]");
|
||||
cx.syn_error(err);
|
||||
Err(())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_lit_into_expr_path(cx: &Ctxt, attr_name: Symbol, lit: &syn::Lit) -> Result<syn::ExprPath, ()> {
|
||||
let string = get_lit_str(cx, attr_name, lit)?;
|
||||
parse_lit_str(string).map_err(|_| cx.error_spanned_by(lit, format!("failed to parse path: {:?}", string.value())))
|
||||
}
|
||||
|
||||
fn get_lit_str<'a>(cx: &Ctxt, attr_name: Symbol, lit: &'a syn::Lit) -> Result<&'a syn::LitStr, ()> {
|
||||
if let syn::Lit::Str(lit) = lit {
|
||||
Ok(lit)
|
||||
} else {
|
||||
cx.error_spanned_by(
|
||||
lit,
|
||||
format!(
|
||||
"expected pb {} attribute to be a string: `{} = \"...\"`",
|
||||
attr_name, attr_name
|
||||
),
|
||||
);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_lit_into_ty(cx: &Ctxt, attr_name: Symbol, lit: &syn::Lit) -> Result<syn::Type, ()> {
|
||||
let string = get_lit_str(cx, attr_name, lit)?;
|
||||
|
||||
parse_lit_str(string).map_err(|_| {
|
||||
cx.error_spanned_by(
|
||||
lit,
|
||||
format!("failed to parse type: {} = {:?}", attr_name, string.value()),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_lit_str<T>(s: &syn::LitStr) -> parse::Result<T>
|
||||
where
|
||||
T: Parse,
|
||||
{
|
||||
let tokens = spanned_tokens(s)?;
|
||||
syn::parse2(tokens)
|
||||
}
|
||||
|
||||
fn spanned_tokens(s: &syn::LitStr) -> parse::Result<TokenStream> {
|
||||
let stream = syn::parse_str(&s.value())?;
|
||||
Ok(respan_token_stream(stream, s.span()))
|
||||
}
|
||||
|
||||
fn respan_token_stream(stream: TokenStream, span: Span) -> TokenStream {
|
||||
stream.into_iter().map(|token| respan_token_tree(token, span)).collect()
|
||||
}
|
||||
|
||||
fn respan_token_tree(mut token: TokenTree, span: Span) -> TokenTree {
|
||||
if let TokenTree::Group(g) = &mut token {
|
||||
*g = Group::new(g.delimiter(), respan_token_stream(g.stream(), span));
|
||||
}
|
||||
token.set_span(span);
|
||||
token
|
||||
}
|
||||
|
||||
fn default_pb_type(ctxt: &Ctxt, ident: &syn::Ident) -> syn::Type {
|
||||
let take_ident = format!("{}", ident.to_string());
|
||||
let lit_str = syn::LitStr::new(&take_ident, ident.span());
|
||||
if let Ok(tokens) = spanned_tokens(&lit_str) {
|
||||
if let Ok(pb_struct_ty) = syn::parse2(tokens) {
|
||||
return pb_struct_ty;
|
||||
}
|
||||
}
|
||||
ctxt.error_spanned_by(ident, format!("❌ Can't find {} protobuf struct", take_ident));
|
||||
panic!()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_option(ty: &syn::Type) -> bool {
|
||||
let path = match ungroup(ty) {
|
||||
syn::Type::Path(ty) => &ty.path,
|
||||
_ => {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
let seg = match path.segments.last() {
|
||||
Some(seg) => seg,
|
||||
None => {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
let args = match &seg.arguments {
|
||||
syn::PathArguments::AngleBracketed(bracketed) => &bracketed.args,
|
||||
_ => {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
seg.ident == "Option" && args.len() == 1
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn ungroup(mut ty: &syn::Type) -> &syn::Type {
|
||||
while let syn::Type::Group(group) = ty {
|
||||
ty = &group.elem;
|
||||
}
|
||||
ty
|
||||
}
|
||||
|
||||
struct BoolAttr<'c>(ASTAttr<'c, ()>);
|
||||
|
||||
impl<'c> BoolAttr<'c> {
|
||||
fn none(cx: &'c Ctxt, name: Symbol) -> Self { BoolAttr(ASTAttr::none(cx, name)) }
|
||||
|
||||
fn set_true<A: ToTokens>(&mut self, obj: A) { self.0.set(obj, ()); }
|
||||
|
||||
fn get(&self) -> bool { self.0.value.is_some() }
|
||||
}
|
42
frontend/rust-lib/flowy-ast/src/ctxt.rs
Normal file
42
frontend/rust-lib/flowy-ast/src/ctxt.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use quote::ToTokens;
|
||||
use std::{cell::RefCell, fmt::Display, thread};
|
||||
use syn;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Ctxt {
|
||||
errors: RefCell<Option<Vec<syn::Error>>>,
|
||||
}
|
||||
|
||||
impl Ctxt {
|
||||
pub fn new() -> Self {
|
||||
Ctxt {
|
||||
errors: RefCell::new(Some(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) {
|
||||
self.errors
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.push(syn::Error::new_spanned(obj.into_token_stream(), msg));
|
||||
}
|
||||
|
||||
pub fn syn_error(&self, err: syn::Error) { self.errors.borrow_mut().as_mut().unwrap().push(err); }
|
||||
|
||||
pub fn check(self) -> Result<(), Vec<syn::Error>> {
|
||||
let errors = self.errors.borrow_mut().take().unwrap();
|
||||
match errors.len() {
|
||||
0 => Ok(()),
|
||||
_ => Err(errors),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Ctxt {
|
||||
fn drop(&mut self) {
|
||||
if !thread::panicking() && self.errors.borrow().is_some() {
|
||||
panic!("forgot to check for errors");
|
||||
}
|
||||
}
|
||||
}
|
38
frontend/rust-lib/flowy-ast/src/event_ast.rs
Normal file
38
frontend/rust-lib/flowy-ast/src/event_ast.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use crate::ASTEnumAttrVariant;
|
||||
|
||||
pub struct EventASTContext {
|
||||
pub event: syn::Ident,
|
||||
pub event_ty: syn::Ident,
|
||||
pub event_request_struct: syn::Ident,
|
||||
pub event_input: Option<syn::Path>,
|
||||
pub event_output: Option<syn::Path>,
|
||||
pub event_error: String,
|
||||
}
|
||||
|
||||
impl EventASTContext {
|
||||
pub fn from(variant: &ASTEnumAttrVariant) -> EventASTContext {
|
||||
let command_name = variant.enum_item_name.clone();
|
||||
if command_name.is_empty() {
|
||||
panic!("Invalid command name: {}", variant.enum_item_name);
|
||||
}
|
||||
|
||||
let event = format_ident!("{}", &command_name);
|
||||
let splits = command_name.split("_").collect::<Vec<&str>>();
|
||||
|
||||
let event_ty = format_ident!("{}", variant.enum_name);
|
||||
let event_request_struct = format_ident!("{}Event", &splits.join(""));
|
||||
|
||||
let event_input = variant.event_input();
|
||||
let event_output = variant.event_output();
|
||||
let event_error = variant.event_error();
|
||||
|
||||
EventASTContext {
|
||||
event,
|
||||
event_ty,
|
||||
event_request_struct,
|
||||
event_input,
|
||||
event_output,
|
||||
event_error,
|
||||
}
|
||||
}
|
||||
}
|
17
frontend/rust-lib/flowy-ast/src/lib.rs
Normal file
17
frontend/rust-lib/flowy-ast/src/lib.rs
Normal file
@ -0,0 +1,17 @@
|
||||
#[macro_use]
|
||||
extern crate syn;
|
||||
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
|
||||
mod ast;
|
||||
mod attr;
|
||||
mod ctxt;
|
||||
|
||||
pub mod event_ast;
|
||||
pub mod symbol;
|
||||
pub mod ty_ext;
|
||||
pub use self::{symbol::*, ty_ext::*};
|
||||
pub use ast::*;
|
||||
pub use attr::*;
|
||||
pub use ctxt::Ctxt;
|
41
frontend/rust-lib/flowy-ast/src/symbol.rs
Normal file
41
frontend/rust-lib/flowy-ast/src/symbol.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use std::fmt::{self, Display};
|
||||
use syn::{Ident, Path};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Symbol(&'static str);
|
||||
pub const PB_ATTRS: Symbol = Symbol("pb");
|
||||
pub const SKIP: Symbol = Symbol("skip"); //#[pb(skip)]
|
||||
pub const PB_INDEX: Symbol = Symbol("index"); //#[pb(index = "1")]
|
||||
pub const PB_ONE_OF: Symbol = Symbol("one_of"); //#[pb(one_of)]
|
||||
pub const DESERIALIZE_WITH: Symbol = Symbol("deserialize_with");
|
||||
pub const SKIP_DESERIALIZING: Symbol = Symbol("skip_deserializing");
|
||||
pub const SERIALIZE_WITH: Symbol = Symbol("serialize_with"); //#[pb(serialize_with = "...")]
|
||||
pub const SKIP_SERIALIZING: Symbol = Symbol("skip_serializing"); //#[pb(skip_serializing)]
|
||||
pub const PB_STRUCT: Symbol = Symbol("struct"); //#[pb(struct="some struct")]
|
||||
pub const PB_ENUM: Symbol = Symbol("enum"); //#[pb(enum="some enum")]
|
||||
|
||||
pub const EVENT_INPUT: Symbol = Symbol("input");
|
||||
pub const EVENT_OUTPUT: Symbol = Symbol("output");
|
||||
pub const EVENT_IGNORE: Symbol = Symbol("ignore");
|
||||
pub const EVENT: Symbol = Symbol("event");
|
||||
pub const EVENT_ERR: Symbol = Symbol("event_err");
|
||||
|
||||
impl PartialEq<Symbol> for Ident {
|
||||
fn eq(&self, word: &Symbol) -> bool { self == word.0 }
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<Symbol> for &'a Ident {
|
||||
fn eq(&self, word: &Symbol) -> bool { *self == word.0 }
|
||||
}
|
||||
|
||||
impl PartialEq<Symbol> for Path {
|
||||
fn eq(&self, word: &Symbol) -> bool { self.is_ident(word.0) }
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<Symbol> for &'a Path {
|
||||
fn eq(&self, word: &Symbol) -> bool { self.is_ident(word.0) }
|
||||
}
|
||||
|
||||
impl Display for Symbol {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str(self.0) }
|
||||
}
|
150
frontend/rust-lib/flowy-ast/src/ty_ext.rs
Normal file
150
frontend/rust-lib/flowy-ast/src/ty_ext.rs
Normal file
@ -0,0 +1,150 @@
|
||||
use crate::Ctxt;
|
||||
use syn::{self, AngleBracketedGenericArguments, PathSegment};
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub enum PrimitiveTy {
|
||||
Map(MapInfo),
|
||||
Vec,
|
||||
Opt,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TyInfo<'a> {
|
||||
pub ident: &'a syn::Ident,
|
||||
pub ty: &'a syn::Type,
|
||||
pub primitive_ty: PrimitiveTy,
|
||||
pub bracket_ty_info: Box<Option<TyInfo<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct MapInfo {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl MapInfo {
|
||||
fn new(key: String, value: String) -> Self { MapInfo { key, value } }
|
||||
}
|
||||
|
||||
impl<'a> TyInfo<'a> {
|
||||
#[allow(dead_code)]
|
||||
pub fn bracketed_ident(&'a self) -> &'a syn::Ident {
|
||||
match self.bracket_ty_info.as_ref() {
|
||||
Some(b_ty) => b_ty.ident,
|
||||
None => {
|
||||
panic!()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_ty<'a>(ctxt: &Ctxt, ty: &'a syn::Type) -> Option<TyInfo<'a>> {
|
||||
// Type -> TypePath -> Path -> PathSegment -> PathArguments ->
|
||||
// AngleBracketedGenericArguments -> GenericArgument -> Type.
|
||||
if let syn::Type::Path(ref p) = ty {
|
||||
if p.path.segments.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let seg = match p.path.segments.last() {
|
||||
Some(seg) => seg,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let _is_option = seg.ident == "Option";
|
||||
|
||||
return if let syn::PathArguments::AngleBracketed(ref bracketed) = seg.arguments {
|
||||
match seg.ident.to_string().as_ref() {
|
||||
"HashMap" => generate_hashmap_ty_info(ctxt, ty, seg, bracketed),
|
||||
"Vec" => generate_vec_ty_info(ctxt, seg, bracketed),
|
||||
"Option" => generate_option_ty_info(ctxt, ty, seg, bracketed),
|
||||
_ => {
|
||||
panic!("Unsupported ty {}", seg.ident.to_string())
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return Some(TyInfo {
|
||||
ident: &seg.ident,
|
||||
ty,
|
||||
primitive_ty: PrimitiveTy::Other,
|
||||
bracket_ty_info: Box::new(None),
|
||||
});
|
||||
};
|
||||
}
|
||||
ctxt.error_spanned_by(ty, format!("Unsupported inner type, get inner type fail"));
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_bracketed(bracketed: &AngleBracketedGenericArguments) -> Vec<&syn::Type> {
|
||||
bracketed
|
||||
.args
|
||||
.iter()
|
||||
.flat_map(|arg| {
|
||||
if let syn::GenericArgument::Type(ref ty_in_bracket) = arg {
|
||||
Some(ty_in_bracket)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<&syn::Type>>()
|
||||
}
|
||||
|
||||
pub fn generate_hashmap_ty_info<'a>(
|
||||
ctxt: &Ctxt,
|
||||
ty: &'a syn::Type,
|
||||
path_segment: &'a PathSegment,
|
||||
bracketed: &'a AngleBracketedGenericArguments,
|
||||
) -> Option<TyInfo<'a>> {
|
||||
// The args of map must greater than 2
|
||||
if bracketed.args.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
let types = parse_bracketed(bracketed);
|
||||
let key = parse_ty(ctxt, types[0]).unwrap().ident.to_string();
|
||||
let value = parse_ty(ctxt, types[1]).unwrap().ident.to_string();
|
||||
let bracket_ty_info = Box::new(parse_ty(ctxt, &types[1]));
|
||||
return Some(TyInfo {
|
||||
ident: &path_segment.ident,
|
||||
ty,
|
||||
primitive_ty: PrimitiveTy::Map(MapInfo::new(key, value)),
|
||||
bracket_ty_info,
|
||||
});
|
||||
}
|
||||
|
||||
fn generate_option_ty_info<'a>(
|
||||
ctxt: &Ctxt,
|
||||
ty: &'a syn::Type,
|
||||
path_segment: &'a PathSegment,
|
||||
bracketed: &'a AngleBracketedGenericArguments,
|
||||
) -> Option<TyInfo<'a>> {
|
||||
assert_eq!(path_segment.ident.to_string(), "Option".to_string());
|
||||
let types = parse_bracketed(bracketed);
|
||||
let bracket_ty_info = Box::new(parse_ty(ctxt, &types[0]));
|
||||
return Some(TyInfo {
|
||||
ident: &path_segment.ident,
|
||||
ty,
|
||||
primitive_ty: PrimitiveTy::Opt,
|
||||
bracket_ty_info,
|
||||
});
|
||||
}
|
||||
|
||||
fn generate_vec_ty_info<'a>(
|
||||
ctxt: &Ctxt,
|
||||
path_segment: &'a PathSegment,
|
||||
bracketed: &'a AngleBracketedGenericArguments,
|
||||
) -> Option<TyInfo<'a>> {
|
||||
if bracketed.args.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
if let syn::GenericArgument::Type(ref bracketed_type) = bracketed.args.first().unwrap() {
|
||||
let bracketed_ty_info = Box::new(parse_ty(ctxt, &bracketed_type));
|
||||
return Some(TyInfo {
|
||||
ident: &path_segment.ident,
|
||||
ty: bracketed_type,
|
||||
primitive_ty: PrimitiveTy::Vec,
|
||||
bracket_ty_info: bracketed_ty_info,
|
||||
});
|
||||
}
|
||||
return None;
|
||||
}
|
1
frontend/rust-lib/flowy-database/.env
Normal file
1
frontend/rust-lib/flowy-database/.env
Normal file
@ -0,0 +1 @@
|
||||
DATABASE_URL=/tmp/database.sql
|
12
frontend/rust-lib/flowy-database/Cargo.toml
Normal file
12
frontend/rust-lib/flowy-database/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "flowy-database"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
diesel = {version = "1.4.8", features = ["sqlite"]}
|
||||
diesel_derives = {version = "1.4.1", features = ["sqlite"]}
|
||||
diesel_migrations = {version = "1.4.0", features = ["sqlite"]}
|
||||
lib-sqlite = {path = "../lib-sqlite" }
|
5
frontend/rust-lib/flowy-database/diesel.toml
Normal file
5
frontend/rust-lib/flowy-database/diesel.toml
Normal file
@ -0,0 +1,5 @@
|
||||
# For documentation on how to configure this file,
|
||||
# see diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/schema.rs"
|
@ -0,0 +1,5 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
DROP TABLE user_table;
|
||||
DROP TABLE workspace_table;
|
||||
DROP TABLE app_table;
|
||||
DROP TABLE view_table;
|
@ -0,0 +1,53 @@
|
||||
-- Your SQL goes here
|
||||
|
||||
CREATE TABLE user_table (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
token TEXT NOT NULL DEFAULT '',
|
||||
email TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE workspace_table (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
desc TEXT NOT NULL DEFAULT '',
|
||||
modified_time BIGINT NOT NULL DEFAULT 0,
|
||||
create_time BIGINT NOT NULL DEFAULT 0,
|
||||
user_id TEXT NOT NULL DEFAULT '',
|
||||
version BIGINT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE app_table (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
workspace_id TEXT NOT NULL DEFAULT '',
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
desc TEXT NOT NULL DEFAULT '',
|
||||
color_style BLOB NOT NULL DEFAULT (x''),
|
||||
last_view_id TEXT DEFAULT '',
|
||||
modified_time BIGINT NOT NULL DEFAULT 0,
|
||||
create_time BIGINT NOT NULL DEFAULT 0,
|
||||
version BIGINT NOT NULL DEFAULT 0,
|
||||
is_trash Boolean NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
CREATE TABLE view_table (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
belong_to_id TEXT NOT NULL DEFAULT '',
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
desc TEXT NOT NULL DEFAULT '',
|
||||
modified_time BIGINT NOT NULL DEFAULT 0,
|
||||
create_time BIGINT NOT NULL DEFAULT 0,
|
||||
thumbnail TEXT NOT NULL DEFAULT '',
|
||||
view_type INTEGER NOT NULL DEFAULT 0,
|
||||
version BIGINT NOT NULL DEFAULT 0,
|
||||
is_trash Boolean NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
CREATE TABLE trash_table (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
desc TEXT NOT NULL DEFAULT '',
|
||||
modified_time BIGINT NOT NULL DEFAULT 0,
|
||||
create_time BIGINT NOT NULL DEFAULT 0,
|
||||
ty INTEGER NOT NULL DEFAULT 0
|
||||
);
|
@ -0,0 +1,2 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
DROP TABLE user_table;
|
@ -0,0 +1,2 @@
|
||||
-- Your SQL goes here
|
||||
ALTER TABLE user_table ADD COLUMN workspace TEXT NOT NULL DEFAULT '';
|
@ -0,0 +1,2 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
DROP TABLE doc_table;
|
@ -0,0 +1,7 @@
|
||||
-- Your SQL goes here
|
||||
CREATE TABLE doc_table (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
-- data BLOB NOT NULL DEFAULT (x''),
|
||||
data TEXT NOT NULL DEFAULT '',
|
||||
rev_id BIGINT NOT NULL DEFAULT 0
|
||||
);
|
@ -0,0 +1 @@
|
||||
-- This file should undo anything in `up.sql`
|
@ -0,0 +1,10 @@
|
||||
-- Your SQL goes here
|
||||
CREATE TABLE rev_table (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
doc_id TEXT NOT NULL DEFAULT '',
|
||||
base_rev_id BIGINT NOT NULL DEFAULT 0,
|
||||
rev_id BIGINT NOT NULL DEFAULT 0,
|
||||
data BLOB NOT NULL DEFAULT (x''),
|
||||
state INTEGER NOT NULL DEFAULT 0,
|
||||
ty INTEGER NOT NULL DEFAULT 0
|
||||
);
|
53
frontend/rust-lib/flowy-database/src/lib.rs
Normal file
53
frontend/rust-lib/flowy-database/src/lib.rs
Normal file
@ -0,0 +1,53 @@
|
||||
pub mod schema;
|
||||
|
||||
#[macro_use]
|
||||
pub mod macros;
|
||||
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
pub use diesel::*;
|
||||
|
||||
#[macro_use]
|
||||
extern crate diesel_derives;
|
||||
pub use diesel_derives::*;
|
||||
|
||||
#[macro_use]
|
||||
extern crate diesel_migrations;
|
||||
|
||||
pub use lib_sqlite::{ConnectionPool, DBConnection, Database};
|
||||
pub type Error = diesel::result::Error;
|
||||
|
||||
use diesel_migrations::*;
|
||||
use lib_sqlite::PoolConfig;
|
||||
use std::{fmt::Debug, io, path::Path};
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::UserDatabaseConnection;
|
||||
pub use diesel::{query_dsl::*, BelongingToDsl, ExpressionMethods, RunQueryDsl};
|
||||
}
|
||||
|
||||
embed_migrations!("../flowy-database/migrations/");
|
||||
pub const DB_NAME: &str = "flowy-database.db";
|
||||
|
||||
pub fn init(storage_path: &str) -> Result<Database, io::Error> {
|
||||
if !Path::new(storage_path).exists() {
|
||||
std::fs::create_dir_all(storage_path)?;
|
||||
}
|
||||
let pool_config = PoolConfig::default();
|
||||
let database = Database::new(storage_path, DB_NAME, pool_config).map_err(as_io_error)?;
|
||||
let conn = database.get_connection().map_err(as_io_error)?;
|
||||
let _ = embedded_migrations::run(&*conn).map_err(as_io_error)?;
|
||||
Ok(database)
|
||||
}
|
||||
|
||||
fn as_io_error<E>(e: E) -> io::Error
|
||||
where
|
||||
E: Into<lib_sqlite::Error> + Debug,
|
||||
{
|
||||
let msg = format!("{:?}", e);
|
||||
io::Error::new(io::ErrorKind::NotConnected, msg)
|
||||
}
|
||||
|
||||
pub trait UserDatabaseConnection: Send + Sync {
|
||||
fn get_connection(&self) -> Result<DBConnection, String>;
|
||||
}
|
162
frontend/rust-lib/flowy-database/src/macros.rs
Normal file
162
frontend/rust-lib/flowy-database/src/macros.rs
Normal file
@ -0,0 +1,162 @@
|
||||
#[rustfmt::skip]
|
||||
/*
|
||||
diesel master support on_conflict on sqlite but not 1.4.7 version. Workaround for this
|
||||
|
||||
match dsl::workspace_table
|
||||
.filter(workspace_table::id.eq(table.id.clone()))
|
||||
.count()
|
||||
.get_result(conn)
|
||||
.unwrap_or(0)
|
||||
{
|
||||
0 => diesel::insert_into(workspace_table::table).values(table)
|
||||
.on_conflict(workspace_table::id)
|
||||
.do_update()
|
||||
.set(WorkspaceTableChangeset::from_table(workspace_table))
|
||||
.execute(conn)?,
|
||||
_ => {
|
||||
let changeset = WorkspaceTableChangeset::from_table(table);
|
||||
let filter = dsl::workspace_table.filter(workspace_table::id.eq(changeset.id.clone()));
|
||||
diesel::update(filter).set(changeset).execute(conn)?;
|
||||
},
|
||||
}
|
||||
|
||||
is equivalent to:
|
||||
|
||||
match diesel_record_count!(workspace_table, &table.id, conn) {
|
||||
0 => diesel_insert_table!(workspace_table, table, conn),
|
||||
_ => diesel_update_table!(workspace_table, WorkspaceTableChangeset::from_table(table), &*conn),
|
||||
}
|
||||
*/
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! diesel_insert_table {
|
||||
(
|
||||
$table_name:ident,
|
||||
$table:expr,
|
||||
$connection:expr
|
||||
) => {
|
||||
{
|
||||
let _ = diesel::insert_into($table_name::table)
|
||||
.values($table.clone())
|
||||
// .on_conflict($table_name::dsl::id)
|
||||
// .do_update()
|
||||
// .set(WorkspaceTableChangeset::from_table(workspace_table))
|
||||
.execute($connection)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! diesel_record_count {
|
||||
(
|
||||
$table_name:ident,
|
||||
$id:expr,
|
||||
$connection:expr
|
||||
) => {
|
||||
$table_name::dsl::$table_name
|
||||
.filter($table_name::dsl::id.eq($id.clone()))
|
||||
.count()
|
||||
.get_result($connection)
|
||||
.unwrap_or(0);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! diesel_update_table {
|
||||
(
|
||||
$table_name:ident,
|
||||
$changeset:expr,
|
||||
$connection:expr
|
||||
) => {{
|
||||
let filter = $table_name::dsl::$table_name.filter($table_name::dsl::id.eq($changeset.id.clone()));
|
||||
let affected_row = diesel::update(filter).set($changeset).execute($connection)?;
|
||||
debug_assert_eq!(affected_row, 1);
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! diesel_delete_table {
|
||||
(
|
||||
$table_name:ident,
|
||||
$id:ident,
|
||||
$connection:ident
|
||||
) => {
|
||||
let filter = $table_name::dsl::$table_name.filter($table_name::dsl::id.eq($id));
|
||||
let affected_row = diesel::delete(filter).execute(&*$connection)?;
|
||||
debug_assert_eq!(affected_row, 1);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_sql_binary_expression {
|
||||
($target:ident) => {
|
||||
impl diesel::serialize::ToSql<diesel::sql_types::Binary, diesel::sqlite::Sqlite> for $target {
|
||||
fn to_sql<W: std::io::Write>(
|
||||
&self,
|
||||
out: &mut diesel::serialize::Output<W, diesel::sqlite::Sqlite>,
|
||||
) -> diesel::serialize::Result {
|
||||
let bytes: Vec<u8> = self.try_into().map_err(|e| format!("{:?}", e))?;
|
||||
diesel::serialize::ToSql::<diesel::sql_types::Binary, diesel::sqlite::Sqlite>::to_sql(&bytes, out)
|
||||
}
|
||||
}
|
||||
// https://docs.diesel.rs/src/diesel/sqlite/types/mod.rs.html#30-33
|
||||
// impl FromSql<sql_types::Binary, Sqlite> for *const [u8] {
|
||||
// fn from_sql(bytes: Option<&SqliteValue>) -> deserialize::Result<Self> {
|
||||
// let bytes = not_none!(bytes).read_blob();
|
||||
// Ok(bytes as *const _)
|
||||
// }
|
||||
// }
|
||||
impl<DB> diesel::deserialize::FromSql<diesel::sql_types::Binary, DB> for $target
|
||||
where
|
||||
DB: diesel::backend::Backend,
|
||||
*const [u8]: diesel::deserialize::FromSql<diesel::sql_types::Binary, DB>,
|
||||
{
|
||||
fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result<Self> {
|
||||
let slice_ptr =
|
||||
<*const [u8] as diesel::deserialize::FromSql<diesel::sql_types::Binary, DB>>::from_sql(bytes)?;
|
||||
let bytes = unsafe { &*slice_ptr };
|
||||
|
||||
match $target::try_from(bytes) {
|
||||
Ok(object) => Ok(object),
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"{:?} deserialize from bytes fail. {:?}",
|
||||
std::any::type_name::<$target>(),
|
||||
e
|
||||
);
|
||||
panic!();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_sql_integer_expression {
|
||||
($target:ident) => {
|
||||
impl<DB> diesel::serialize::ToSql<Integer, DB> for $target
|
||||
where
|
||||
DB: diesel::backend::Backend,
|
||||
i32: diesel::serialize::ToSql<Integer, DB>,
|
||||
{
|
||||
fn to_sql<W: std::io::Write>(
|
||||
&self,
|
||||
out: &mut diesel::serialize::Output<W, DB>,
|
||||
) -> diesel::serialize::Result {
|
||||
(*self as i32).to_sql(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> diesel::deserialize::FromSql<Integer, DB> for $target
|
||||
where
|
||||
DB: diesel::backend::Backend,
|
||||
i32: diesel::deserialize::FromSql<Integer, DB>,
|
||||
{
|
||||
fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result<Self> {
|
||||
let smaill_int = i32::from_sql(bytes)?;
|
||||
Ok($target::from(smaill_int))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
92
frontend/rust-lib/flowy-database/src/schema.rs
Normal file
92
frontend/rust-lib/flowy-database/src/schema.rs
Normal file
@ -0,0 +1,92 @@
|
||||
table! {
|
||||
app_table (id) {
|
||||
id -> Text,
|
||||
workspace_id -> Text,
|
||||
name -> Text,
|
||||
desc -> Text,
|
||||
color_style -> Binary,
|
||||
last_view_id -> Nullable<Text>,
|
||||
modified_time -> BigInt,
|
||||
create_time -> BigInt,
|
||||
version -> BigInt,
|
||||
is_trash -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
doc_table (id) {
|
||||
id -> Text,
|
||||
data -> Text,
|
||||
rev_id -> BigInt,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
rev_table (id) {
|
||||
id -> Integer,
|
||||
doc_id -> Text,
|
||||
base_rev_id -> BigInt,
|
||||
rev_id -> BigInt,
|
||||
data -> Binary,
|
||||
state -> Integer,
|
||||
ty -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
trash_table (id) {
|
||||
id -> Text,
|
||||
name -> Text,
|
||||
desc -> Text,
|
||||
modified_time -> BigInt,
|
||||
create_time -> BigInt,
|
||||
ty -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
user_table (id) {
|
||||
id -> Text,
|
||||
name -> Text,
|
||||
token -> Text,
|
||||
email -> Text,
|
||||
workspace -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
view_table (id) {
|
||||
id -> Text,
|
||||
belong_to_id -> Text,
|
||||
name -> Text,
|
||||
desc -> Text,
|
||||
modified_time -> BigInt,
|
||||
create_time -> BigInt,
|
||||
thumbnail -> Text,
|
||||
view_type -> Integer,
|
||||
version -> BigInt,
|
||||
is_trash -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
workspace_table (id) {
|
||||
id -> Text,
|
||||
name -> Text,
|
||||
desc -> Text,
|
||||
modified_time -> BigInt,
|
||||
create_time -> BigInt,
|
||||
user_id -> Text,
|
||||
version -> BigInt,
|
||||
}
|
||||
}
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
app_table,
|
||||
doc_table,
|
||||
rev_table,
|
||||
trash_table,
|
||||
user_table,
|
||||
view_table,
|
||||
workspace_table,
|
||||
);
|
2
frontend/rust-lib/flowy-derive/.gitignore
vendored
Normal file
2
frontend/rust-lib/flowy-derive/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
25
frontend/rust-lib/flowy-derive/Cargo.toml
Normal file
25
frontend/rust-lib/flowy-derive/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "flowy-derive"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
name = "flowy_derive"
|
||||
|
||||
[[test]]
|
||||
name = "tests"
|
||||
path = "tests/progress.rs"
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1.0.60", features = ["extra-traits", "visit"] }
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
flowy-ast = { path = "../flowy-ast" }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
trybuild = "1.0.40"
|
||||
log = "0.4.11"
|
41
frontend/rust-lib/flowy-derive/src/dart_event/mod.rs
Normal file
41
frontend/rust-lib/flowy-derive/src/dart_event/mod.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
// #[proc_macro_derive(DartEvent, attributes(event_ty))]
|
||||
pub fn expand_enum_derive(_input: &syn::DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
|
||||
Ok(TokenStream::default())
|
||||
}
|
||||
|
||||
// use flowy_ast::{ASTContainer, Ctxt};
|
||||
// use proc_macro2::TokenStream;
|
||||
//
|
||||
// // #[proc_macro_derive(DartEvent, attributes(event_ty))]
|
||||
// pub fn expand_enum_derive(input: &syn::DeriveInput) -> Result<TokenStream,
|
||||
// Vec<syn::Error>> { let ctxt = Ctxt::new();
|
||||
// let cont = match ASTContainer::from_ast(&ctxt, input) {
|
||||
// Some(cont) => cont,
|
||||
// None => return Err(ctxt.check().unwrap_err()),
|
||||
// };
|
||||
//
|
||||
// let enum_ident = &cont.ident;
|
||||
// let pb_enum = cont.attrs.pb_enum_type().unwrap();
|
||||
//
|
||||
// let build_display_pb_enum = cont.data.all_idents().map(|i| {
|
||||
// let a = format_ident!("{}", i.to_string());
|
||||
// let token_stream: TokenStream = quote! {
|
||||
// #enum_ident::#i => f.write_str(&#a)?,
|
||||
// };
|
||||
// token_stream
|
||||
// });
|
||||
//
|
||||
// ctxt.check()?;
|
||||
//
|
||||
// Ok(quote! {
|
||||
// impl std::fmt::Display for #enum_ident {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
|
||||
// { match self {
|
||||
// #(#build_display_pb_enum)*
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// }
|
@ -0,0 +1,99 @@
|
||||
pub enum TypeCategory {
|
||||
Array,
|
||||
Map,
|
||||
Str,
|
||||
Protobuf,
|
||||
Bytes,
|
||||
Enum,
|
||||
Opt,
|
||||
Primitive,
|
||||
}
|
||||
// auto generate, do not edit
|
||||
pub fn category_from_str(type_str: &str) -> TypeCategory {
|
||||
match type_str {
|
||||
"Vec" => TypeCategory::Array,
|
||||
"HashMap" => TypeCategory::Map,
|
||||
"u8" => TypeCategory::Bytes,
|
||||
"String" => TypeCategory::Str,
|
||||
"QueryAppRequest"
|
||||
| "AppIdentifier"
|
||||
| "CreateAppRequest"
|
||||
| "ColorStyle"
|
||||
| "CreateAppParams"
|
||||
| "App"
|
||||
| "RepeatedApp"
|
||||
| "UpdateAppRequest"
|
||||
| "UpdateAppParams"
|
||||
| "UpdateWorkspaceRequest"
|
||||
| "UpdateWorkspaceParams"
|
||||
| "CurrentWorkspaceSetting"
|
||||
| "CreateWorkspaceRequest"
|
||||
| "CreateWorkspaceParams"
|
||||
| "Workspace"
|
||||
| "RepeatedWorkspace"
|
||||
| "QueryWorkspaceRequest"
|
||||
| "WorkspaceIdentifier"
|
||||
| "TrashIdentifiers"
|
||||
| "TrashIdentifier"
|
||||
| "Trash"
|
||||
| "RepeatedTrash"
|
||||
| "UpdateViewRequest"
|
||||
| "UpdateViewParams"
|
||||
| "QueryViewRequest"
|
||||
| "ViewIdentifier"
|
||||
| "ViewIdentifiers"
|
||||
| "CreateViewRequest"
|
||||
| "CreateViewParams"
|
||||
| "View"
|
||||
| "RepeatedView"
|
||||
| "ExportRequest"
|
||||
| "ExportData"
|
||||
| "CreateDocParams"
|
||||
| "Doc"
|
||||
| "UpdateDocParams"
|
||||
| "DocDelta"
|
||||
| "NewDocUser"
|
||||
| "DocIdentifier"
|
||||
| "RevId"
|
||||
| "Revision"
|
||||
| "RevisionRange"
|
||||
| "WsDocumentData"
|
||||
| "KeyValue"
|
||||
| "WorkspaceError"
|
||||
| "WsError"
|
||||
| "WsMessage"
|
||||
| "SignInRequest"
|
||||
| "SignInParams"
|
||||
| "SignInResponse"
|
||||
| "SignUpRequest"
|
||||
| "SignUpParams"
|
||||
| "SignUpResponse"
|
||||
| "UserToken"
|
||||
| "UserProfile"
|
||||
| "UpdateUserRequest"
|
||||
| "UpdateUserParams"
|
||||
| "DocError"
|
||||
| "FFIRequest"
|
||||
| "FFIResponse"
|
||||
| "SubscribeObject"
|
||||
| "UserError"
|
||||
=> TypeCategory::Protobuf,
|
||||
"TrashType"
|
||||
| "ViewType"
|
||||
| "ExportType"
|
||||
| "ErrorCode"
|
||||
| "RevType"
|
||||
| "WsDataType"
|
||||
| "WorkspaceEvent"
|
||||
| "WorkspaceNotification"
|
||||
| "WsModule"
|
||||
| "DocObservable"
|
||||
| "FFIStatusCode"
|
||||
| "UserEvent"
|
||||
| "UserNotification"
|
||||
=> TypeCategory::Enum,
|
||||
|
||||
"Option" => TypeCategory::Opt,
|
||||
_ => TypeCategory::Primitive,
|
||||
}
|
||||
}
|
3
frontend/rust-lib/flowy-derive/src/derive_cache/mod.rs
Normal file
3
frontend/rust-lib/flowy-derive/src/derive_cache/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod derive_cache;
|
||||
|
||||
pub use derive_cache::*;
|
43
frontend/rust-lib/flowy-derive/src/lib.rs
Normal file
43
frontend/rust-lib/flowy-derive/src/lib.rs
Normal file
@ -0,0 +1,43 @@
|
||||
// https://docs.rs/syn/1.0.48/syn/struct.DeriveInput.html
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
|
||||
mod dart_event;
|
||||
mod derive_cache;
|
||||
mod proto_buf;
|
||||
|
||||
// Inspired by https://serde.rs/attributes.html
|
||||
#[proc_macro_derive(ProtoBuf, attributes(pb))]
|
||||
pub fn derive_proto_buf(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
proto_buf::expand_derive(&input)
|
||||
.unwrap_or_else(to_compile_errors)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ProtoBuf_Enum, attributes(pb))]
|
||||
pub fn derive_proto_buf_enum(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
proto_buf::expand_enum_derive(&input)
|
||||
.unwrap_or_else(to_compile_errors)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Flowy_Event, attributes(event, event_err))]
|
||||
pub fn derive_dart_event(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
dart_event::expand_enum_derive(&input)
|
||||
.unwrap_or_else(to_compile_errors)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
|
||||
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
|
||||
quote!(#(#compile_errors)*)
|
||||
}
|
221
frontend/rust-lib/flowy-derive/src/proto_buf/deserialize.rs
Normal file
221
frontend/rust-lib/flowy-derive/src/proto_buf/deserialize.rs
Normal file
@ -0,0 +1,221 @@
|
||||
use crate::{derive_cache::TypeCategory, proto_buf::util::*};
|
||||
use flowy_ast::*;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
pub fn make_de_token_steam(ctxt: &Ctxt, ast: &ASTContainer) -> Option<TokenStream> {
|
||||
let pb_ty = ast.attrs.pb_struct_type()?;
|
||||
let struct_ident = &ast.ident;
|
||||
|
||||
let build_take_fields = ast
|
||||
.data
|
||||
.all_fields()
|
||||
.filter(|f| !f.attrs.skip_deserializing())
|
||||
.flat_map(|field| {
|
||||
if let Some(func) = field.attrs.deserialize_with() {
|
||||
let member = &field.member;
|
||||
Some(quote! { o.#member=#struct_ident::#func(pb); })
|
||||
} else if field.attrs.is_one_of() {
|
||||
token_stream_for_one_of(ctxt, field)
|
||||
} else {
|
||||
token_stream_for_field(ctxt, &field.member, &field.ty, false)
|
||||
}
|
||||
});
|
||||
|
||||
let de_token_stream: TokenStream = quote! {
|
||||
impl std::convert::TryFrom<bytes::Bytes> for #struct_ident {
|
||||
type Error = ::protobuf::ProtobufError;
|
||||
fn try_from(bytes: bytes::Bytes) -> Result<Self, Self::Error> {
|
||||
let mut pb: crate::protobuf::#pb_ty = ::protobuf::Message::parse_from_bytes(&bytes)?;
|
||||
#struct_ident::try_from(&mut pb)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&mut crate::protobuf::#pb_ty> for #struct_ident {
|
||||
type Error = ::protobuf::ProtobufError;
|
||||
fn try_from(pb: &mut crate::protobuf::#pb_ty) -> Result<Self, Self::Error> {
|
||||
let mut o = Self::default();
|
||||
#(#build_take_fields)*
|
||||
Ok(o)
|
||||
}
|
||||
}
|
||||
}
|
||||
.into();
|
||||
|
||||
Some(de_token_stream)
|
||||
// None
|
||||
}
|
||||
|
||||
fn token_stream_for_one_of(ctxt: &Ctxt, field: &ASTField) -> Option<TokenStream> {
|
||||
let member = &field.member;
|
||||
let ident = get_member_ident(ctxt, member)?;
|
||||
let ty_info = parse_ty(ctxt, &field.ty)?;
|
||||
let bracketed_ty_info = ty_info.bracket_ty_info.as_ref().as_ref();
|
||||
|
||||
let has_func = format_ident!("has_{}", ident.to_string());
|
||||
// eprintln!("😁{:#?}", ty_info.primitive_ty);
|
||||
// eprintln!("{:#?}", ty_info.bracket_ty_info);
|
||||
match ident_category(bracketed_ty_info.unwrap().ident) {
|
||||
TypeCategory::Enum => {
|
||||
let get_func = format_ident!("get_{}", ident.to_string());
|
||||
let ty = ty_info.ty;
|
||||
Some(quote! {
|
||||
if pb.#has_func() {
|
||||
let enum_de_from_pb = #ty::try_from(&pb.#get_func()).unwrap();
|
||||
o.#member = Some(enum_de_from_pb);
|
||||
}
|
||||
})
|
||||
},
|
||||
TypeCategory::Primitive => {
|
||||
let get_func = format_ident!("get_{}", ident.to_string());
|
||||
Some(quote! {
|
||||
if pb.#has_func() {
|
||||
o.#member=Some(pb.#get_func());
|
||||
}
|
||||
})
|
||||
},
|
||||
TypeCategory::Str => {
|
||||
let take_func = format_ident!("take_{}", ident.to_string());
|
||||
Some(quote! {
|
||||
if pb.#has_func() {
|
||||
o.#member=Some(pb.#take_func());
|
||||
}
|
||||
})
|
||||
},
|
||||
TypeCategory::Array => {
|
||||
let take_func = format_ident!("take_{}", ident.to_string());
|
||||
Some(quote! {
|
||||
if pb.#has_func() {
|
||||
o.#member=Some(pb.#take_func());
|
||||
}
|
||||
})
|
||||
},
|
||||
_ => {
|
||||
let take_func = format_ident!("take_{}", ident.to_string());
|
||||
let ty = bracketed_ty_info.unwrap().ty;
|
||||
Some(quote! {
|
||||
if pb.#has_func() {
|
||||
let val = #ty::try_from(&mut pb.#take_func()).unwrap();
|
||||
o.#member=Some(val);
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn token_stream_for_field(ctxt: &Ctxt, member: &syn::Member, ty: &syn::Type, is_option: bool) -> Option<TokenStream> {
|
||||
let ident = get_member_ident(ctxt, member)?;
|
||||
let ty_info = parse_ty(ctxt, ty)?;
|
||||
match ident_category(ty_info.ident) {
|
||||
TypeCategory::Array => {
|
||||
assert_bracket_ty_is_some(ctxt, &ty_info);
|
||||
token_stream_for_vec(ctxt, &member, &ty_info.bracket_ty_info.unwrap())
|
||||
},
|
||||
TypeCategory::Map => {
|
||||
assert_bracket_ty_is_some(ctxt, &ty_info);
|
||||
token_stream_for_map(ctxt, &member, &ty_info.bracket_ty_info.unwrap())
|
||||
},
|
||||
TypeCategory::Protobuf => {
|
||||
// if the type wrapped by SingularPtrField, should call take first
|
||||
let take = syn::Ident::new("take", Span::call_site());
|
||||
// inner_type_ty would be the type of the field. (e.g value of AnyData)
|
||||
let ty = ty_info.ty;
|
||||
Some(quote! {
|
||||
let some_value = pb.#member.#take();
|
||||
if some_value.is_some() {
|
||||
let struct_de_from_pb = #ty::try_from(&mut some_value.unwrap()).unwrap();
|
||||
o.#member = struct_de_from_pb;
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
TypeCategory::Enum => {
|
||||
let ty = ty_info.ty;
|
||||
Some(quote! {
|
||||
let enum_de_from_pb = #ty::try_from(&pb.#member).unwrap();
|
||||
o.#member = enum_de_from_pb;
|
||||
|
||||
})
|
||||
},
|
||||
TypeCategory::Str => {
|
||||
let take_ident = syn::Ident::new(&format!("take_{}", ident.to_string()), Span::call_site());
|
||||
if is_option {
|
||||
Some(quote! {
|
||||
if pb.#member.is_empty() {
|
||||
o.#member = None;
|
||||
} else {
|
||||
o.#member = Some(pb.#take_ident());
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Some(quote! {
|
||||
o.#member = pb.#take_ident();
|
||||
})
|
||||
}
|
||||
},
|
||||
TypeCategory::Opt => token_stream_for_field(ctxt, member, ty_info.bracket_ty_info.unwrap().ty, true),
|
||||
TypeCategory::Primitive | TypeCategory::Bytes => {
|
||||
// eprintln!("😄 #{:?}", &field.name().unwrap());
|
||||
if is_option {
|
||||
Some(quote! { o.#member = Some(pb.#member.clone()); })
|
||||
} else {
|
||||
Some(quote! { o.#member = pb.#member.clone(); })
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn token_stream_for_vec(ctxt: &Ctxt, member: &syn::Member, bracketed_type: &TyInfo) -> Option<TokenStream> {
|
||||
let ident = get_member_ident(ctxt, member)?;
|
||||
|
||||
match ident_category(bracketed_type.ident) {
|
||||
TypeCategory::Protobuf => {
|
||||
let ty = bracketed_type.ty;
|
||||
// Deserialize from pb struct of type vec, should call take_xx(), get the
|
||||
// repeated_field and then calling the into_iter。
|
||||
let take_ident = format_ident!("take_{}", ident.to_string());
|
||||
Some(quote! {
|
||||
o.#member = pb.#take_ident()
|
||||
.into_iter()
|
||||
.map(|mut m| #ty::try_from(&mut m).unwrap())
|
||||
.collect();
|
||||
})
|
||||
},
|
||||
TypeCategory::Bytes => {
|
||||
// Vec<u8>
|
||||
Some(quote! {
|
||||
o.#member = pb.#member.clone();
|
||||
})
|
||||
},
|
||||
_ => {
|
||||
// String
|
||||
let take_ident = format_ident!("take_{}", ident.to_string());
|
||||
Some(quote! {
|
||||
o.#member = pb.#take_ident().into_vec();
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn token_stream_for_map(ctxt: &Ctxt, member: &syn::Member, bracketed_type: &TyInfo) -> Option<TokenStream> {
|
||||
let ident = get_member_ident(ctxt, member)?;
|
||||
|
||||
let take_ident = format_ident!("take_{}", ident.to_string());
|
||||
let ty = bracketed_type.ty;
|
||||
|
||||
match ident_category(bracketed_type.ident) {
|
||||
TypeCategory::Protobuf => Some(quote! {
|
||||
let mut m: std::collections::HashMap<String, #ty> = std::collections::HashMap::new();
|
||||
pb.#take_ident().into_iter().for_each(|(k,mut v)| {
|
||||
m.insert(k.clone(), #ty::try_from(&mut v).unwrap());
|
||||
});
|
||||
o.#member = m;
|
||||
}),
|
||||
_ => Some(quote! {
|
||||
let mut m: std::collections::HashMap<String, #ty> = std::collections::HashMap::new();
|
||||
pb.#take_ident().into_iter().for_each(|(k,mut v)| {
|
||||
m.insert(k.clone(), v);
|
||||
});
|
||||
o.#member = m;
|
||||
}),
|
||||
}
|
||||
}
|
41
frontend/rust-lib/flowy-derive/src/proto_buf/enum_serde.rs
Normal file
41
frontend/rust-lib/flowy-derive/src/proto_buf/enum_serde.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use flowy_ast::*;
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn make_enum_token_stream(_ctxt: &Ctxt, cont: &ASTContainer) -> Option<TokenStream> {
|
||||
let enum_ident = &cont.ident;
|
||||
let pb_enum = cont.attrs.pb_enum_type()?;
|
||||
let build_to_pb_enum = cont.data.all_idents().map(|i| {
|
||||
let token_stream: TokenStream = quote! {
|
||||
#enum_ident::#i => crate::protobuf::#pb_enum::#i,
|
||||
};
|
||||
token_stream
|
||||
});
|
||||
|
||||
let build_from_pb_enum = cont.data.all_idents().map(|i| {
|
||||
let token_stream: TokenStream = quote! {
|
||||
crate::protobuf::#pb_enum::#i => #enum_ident::#i,
|
||||
};
|
||||
token_stream
|
||||
});
|
||||
|
||||
Some(quote! {
|
||||
impl std::convert::TryFrom<&crate::protobuf::#pb_enum> for #enum_ident {
|
||||
type Error = String;
|
||||
fn try_from(pb:&crate::protobuf::#pb_enum) -> Result<Self, Self::Error> {
|
||||
Ok(match pb {
|
||||
#(#build_from_pb_enum)*
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryInto<crate::protobuf::#pb_enum> for #enum_ident {
|
||||
type Error = String;
|
||||
fn try_into(self) -> Result<crate::protobuf::#pb_enum, Self::Error> {
|
||||
Ok(match self {
|
||||
#(#build_to_pb_enum)*
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
79
frontend/rust-lib/flowy-derive/src/proto_buf/mod.rs
Normal file
79
frontend/rust-lib/flowy-derive/src/proto_buf/mod.rs
Normal file
@ -0,0 +1,79 @@
|
||||
mod deserialize;
|
||||
mod enum_serde;
|
||||
mod serialize;
|
||||
mod util;
|
||||
|
||||
use crate::proto_buf::{
|
||||
deserialize::make_de_token_steam,
|
||||
enum_serde::make_enum_token_stream,
|
||||
serialize::make_se_token_stream,
|
||||
};
|
||||
use flowy_ast::*;
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
pub fn expand_derive(input: &syn::DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
|
||||
let ctxt = Ctxt::new();
|
||||
let cont = match ASTContainer::from_ast(&ctxt, input) {
|
||||
Some(cont) => cont,
|
||||
None => return Err(ctxt.check().unwrap_err()),
|
||||
};
|
||||
|
||||
let mut token_stream: TokenStream = TokenStream::default();
|
||||
|
||||
let de_token_stream = make_de_token_steam(&ctxt, &cont);
|
||||
if de_token_stream.is_some() {
|
||||
token_stream.extend(de_token_stream.unwrap());
|
||||
}
|
||||
|
||||
let se_token_stream = make_se_token_stream(&ctxt, &cont);
|
||||
if se_token_stream.is_some() {
|
||||
token_stream.extend(se_token_stream.unwrap());
|
||||
}
|
||||
|
||||
ctxt.check()?;
|
||||
Ok(token_stream)
|
||||
}
|
||||
|
||||
pub fn expand_enum_derive(input: &syn::DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
|
||||
let ctxt = Ctxt::new();
|
||||
let cont = match ASTContainer::from_ast(&ctxt, input) {
|
||||
Some(cont) => cont,
|
||||
None => return Err(ctxt.check().unwrap_err()),
|
||||
};
|
||||
|
||||
let mut token_stream: TokenStream = TokenStream::default();
|
||||
|
||||
let enum_token_stream = make_enum_token_stream(&ctxt, &cont);
|
||||
if enum_token_stream.is_some() {
|
||||
token_stream.extend(enum_token_stream.unwrap());
|
||||
}
|
||||
|
||||
ctxt.check()?;
|
||||
Ok(token_stream)
|
||||
}
|
||||
// #[macro_use]
|
||||
// macro_rules! impl_try_for_primitive_type {
|
||||
// ($target:ident) => {
|
||||
// impl std::convert::TryFrom<&$target> for $target {
|
||||
// type Error = String;
|
||||
// fn try_from(val: &$target) -> Result<Self, Self::Error> {
|
||||
// Ok(val.clone()) } }
|
||||
//
|
||||
// impl std::convert::TryInto<$target> for $target {
|
||||
// type Error = String;
|
||||
//
|
||||
// fn try_into(self) -> Result<Self, Self::Error> { Ok(self) }
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// impl_try_for_primitive_type!(String);
|
||||
// impl_try_for_primitive_type!(i64);
|
||||
// impl_try_for_primitive_type!(i32);
|
||||
// impl_try_for_primitive_type!(i16);
|
||||
// impl_try_for_primitive_type!(u64);
|
||||
// impl_try_for_primitive_type!(u32);
|
||||
// impl_try_for_primitive_type!(u16);
|
||||
// impl_try_for_primitive_type!(bool);
|
||||
// impl_try_for_primitive_type!(f64);
|
||||
// impl_try_for_primitive_type!(f32);
|
160
frontend/rust-lib/flowy-derive/src/proto_buf/serialize.rs
Normal file
160
frontend/rust-lib/flowy-derive/src/proto_buf/serialize.rs
Normal file
@ -0,0 +1,160 @@
|
||||
use crate::{
|
||||
derive_cache::TypeCategory,
|
||||
proto_buf::util::{get_member_ident, ident_category},
|
||||
};
|
||||
use flowy_ast::*;
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
pub fn make_se_token_stream(ctxt: &Ctxt, ast: &ASTContainer) -> Option<TokenStream> {
|
||||
let pb_ty = ast.attrs.pb_struct_type()?;
|
||||
let struct_ident = &ast.ident;
|
||||
|
||||
let build_set_pb_fields = ast
|
||||
.data
|
||||
.all_fields()
|
||||
.filter(|f| !f.attrs.skip_serializing())
|
||||
.flat_map(|field| se_token_stream_for_field(&ctxt, &field, false));
|
||||
|
||||
let se_token_stream: TokenStream = quote! {
|
||||
|
||||
impl std::convert::TryInto<bytes::Bytes> for #struct_ident {
|
||||
type Error = ::protobuf::ProtobufError;
|
||||
fn try_into(self) -> Result<bytes::Bytes, Self::Error> {
|
||||
use protobuf::Message;
|
||||
let pb: crate::protobuf::#pb_ty = self.try_into()?;
|
||||
let bytes = pb.write_to_bytes()?;
|
||||
Ok(bytes::Bytes::from(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryInto<crate::protobuf::#pb_ty> for #struct_ident {
|
||||
type Error = ::protobuf::ProtobufError;
|
||||
fn try_into(self) -> Result<crate::protobuf::#pb_ty, Self::Error> {
|
||||
let mut pb = crate::protobuf::#pb_ty::new();
|
||||
#(#build_set_pb_fields)*
|
||||
Ok(pb)
|
||||
}
|
||||
}
|
||||
}
|
||||
.into();
|
||||
|
||||
Some(se_token_stream)
|
||||
}
|
||||
|
||||
fn se_token_stream_for_field(ctxt: &Ctxt, field: &ASTField, _take: bool) -> Option<TokenStream> {
|
||||
if let Some(func) = &field.attrs.serialize_with() {
|
||||
let member = &field.member;
|
||||
Some(quote! { pb.#member=self.#func(); })
|
||||
} else if field.attrs.is_one_of() {
|
||||
token_stream_for_one_of(ctxt, field)
|
||||
} else {
|
||||
gen_token_stream(ctxt, &field.member, &field.ty, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn token_stream_for_one_of(ctxt: &Ctxt, field: &ASTField) -> Option<TokenStream> {
|
||||
let member = &field.member;
|
||||
let ident = get_member_ident(ctxt, member)?;
|
||||
let ty_info = parse_ty(ctxt, &field.ty)?;
|
||||
let bracketed_ty_info = ty_info.bracket_ty_info.as_ref().as_ref();
|
||||
|
||||
let set_func = format_ident!("set_{}", ident.to_string());
|
||||
|
||||
match ident_category(bracketed_ty_info.unwrap().ident) {
|
||||
TypeCategory::Protobuf => Some(quote! {
|
||||
match self.#member {
|
||||
Some(s) => { pb.#set_func(s.try_into().unwrap()) }
|
||||
None => {}
|
||||
}
|
||||
}),
|
||||
_ => Some(quote! {
|
||||
match self.#member {
|
||||
Some(ref s) => { pb.#set_func(s.clone()) }
|
||||
None => {}
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_token_stream(ctxt: &Ctxt, member: &syn::Member, ty: &syn::Type, is_option: bool) -> Option<TokenStream> {
|
||||
let ty_info = parse_ty(ctxt, ty)?;
|
||||
match ident_category(ty_info.ident) {
|
||||
TypeCategory::Array => token_stream_for_vec(ctxt, &member, &ty_info.ty),
|
||||
TypeCategory::Map => token_stream_for_map(ctxt, &member, &ty_info.bracket_ty_info.unwrap().ty),
|
||||
TypeCategory::Str => {
|
||||
if is_option {
|
||||
Some(quote! {
|
||||
match self.#member {
|
||||
Some(ref s) => { pb.#member = s.to_string().clone(); }
|
||||
None => { pb.#member = String::new(); }
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Some(quote! { pb.#member = self.#member.clone(); })
|
||||
}
|
||||
},
|
||||
TypeCategory::Protobuf => {
|
||||
Some(quote! { pb.#member = ::protobuf::SingularPtrField::some(self.#member.try_into().unwrap()); })
|
||||
},
|
||||
TypeCategory::Opt => gen_token_stream(ctxt, member, ty_info.bracket_ty_info.unwrap().ty, true),
|
||||
TypeCategory::Enum => {
|
||||
// let pb_enum_ident = format_ident!("{}", ty_info.ident.to_string());
|
||||
// Some(quote! {
|
||||
// flowy_protobuf::#pb_enum_ident::from_i32(self.#member.value()).unwrap();
|
||||
// })
|
||||
Some(quote! {
|
||||
pb.#member = self.#member.try_into().unwrap();
|
||||
})
|
||||
},
|
||||
_ => Some(quote! { pb.#member = self.#member; }),
|
||||
}
|
||||
}
|
||||
|
||||
// e.g. pub cells: Vec<CellData>, the memeber will be cells, ty would be Vec
|
||||
fn token_stream_for_vec(ctxt: &Ctxt, member: &syn::Member, ty: &syn::Type) -> Option<TokenStream> {
|
||||
let ty_info = parse_ty(ctxt, ty)?;
|
||||
match ident_category(ty_info.ident) {
|
||||
TypeCategory::Protobuf => Some(quote! {
|
||||
pb.#member = ::protobuf::RepeatedField::from_vec(
|
||||
self.#member
|
||||
.into_iter()
|
||||
.map(|m| m.try_into().unwrap())
|
||||
.collect());
|
||||
}),
|
||||
TypeCategory::Bytes => Some(quote! { pb.#member = self.#member.clone(); }),
|
||||
|
||||
_ => Some(quote! {
|
||||
pb.#member = ::protobuf::RepeatedField::from_vec(self.#member.clone());
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// e.g. pub cells: HashMap<xx, xx>
|
||||
fn token_stream_for_map(ctxt: &Ctxt, member: &syn::Member, ty: &syn::Type) -> Option<TokenStream> {
|
||||
// The key of the hashmap must be string
|
||||
let flowy_protobuf = format_ident!("flowy_protobuf");
|
||||
let ty_info = parse_ty(ctxt, ty)?;
|
||||
match ident_category(ty_info.ident) {
|
||||
TypeCategory::Protobuf => {
|
||||
let value_type = ty_info.ident;
|
||||
Some(quote! {
|
||||
let mut m: std::collections::HashMap<String, #flowy_protobuf::#value_type> = std::collections::HashMap::new();
|
||||
self.#member.iter().for_each(|(k,v)| {
|
||||
m.insert(k.clone(), v.try_into().unwrap());
|
||||
});
|
||||
pb.#member = m;
|
||||
})
|
||||
},
|
||||
|
||||
_ => {
|
||||
let value_type = ty_info.ident;
|
||||
Some(quote! {
|
||||
let mut m: std::collections::HashMap<String, #flowy_protobuf::#value_type> = std::collections::HashMap::new();
|
||||
self.#member.iter().for_each(|(k,v)| {
|
||||
m.insert(k.clone(), v.clone());
|
||||
});
|
||||
pb.#member = m;
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
22
frontend/rust-lib/flowy-derive/src/proto_buf/util.rs
Normal file
22
frontend/rust-lib/flowy-derive/src/proto_buf/util.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use crate::derive_cache::*;
|
||||
use flowy_ast::{Ctxt, TyInfo};
|
||||
|
||||
pub fn ident_category(ident: &syn::Ident) -> TypeCategory {
|
||||
let ident_str: &str = &ident.to_string();
|
||||
category_from_str(ident_str)
|
||||
}
|
||||
|
||||
pub(crate) fn get_member_ident<'a>(ctxt: &Ctxt, member: &'a syn::Member) -> Option<&'a syn::Ident> {
|
||||
if let syn::Member::Named(ref ident) = member {
|
||||
Some(ident)
|
||||
} else {
|
||||
ctxt.error_spanned_by(member, format!("Unsupported member, shouldn't be self.0"));
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_bracket_ty_is_some(ctxt: &Ctxt, ty_info: &TyInfo) {
|
||||
if ty_info.bracket_ty_info.is_none() {
|
||||
ctxt.error_spanned_by(ty_info.ty, format!("Invalid bracketed type when gen de token steam"));
|
||||
}
|
||||
}
|
5
frontend/rust-lib/flowy-derive/tests/progress.rs
Normal file
5
frontend/rust-lib/flowy-derive/tests/progress.rs
Normal file
@ -0,0 +1,5 @@
|
||||
#[tokio::test]
|
||||
async fn tests() {
|
||||
let _t = trybuild::TestCases::new();
|
||||
// t.pass("tests/protobuf_enum.rs");
|
||||
}
|
21
frontend/rust-lib/flowy-document-infra/Cargo.toml
Normal file
21
frontend/rust-lib/flowy-document-infra/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "flowy-document-infra"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
lib-ot = { path = "../lib-ot" }
|
||||
flowy-derive = { path = "../flowy-derive" }
|
||||
protobuf = {version = "2.18.0"}
|
||||
bytes = "1.0"
|
||||
log = "0.4.14"
|
||||
md5 = "0.7.0"
|
||||
tokio = {version = "1", features = ["sync"]}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
url = "2.2"
|
||||
strum = "0.21"
|
||||
strum_macros = "0.21"
|
||||
chrono = "0.4.19"
|
3
frontend/rust-lib/flowy-document-infra/Flowy.toml
Normal file
3
frontend/rust-lib/flowy-document-infra/Flowy.toml
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
proto_crates = ["src/entities"]
|
||||
event_files = []
|
1
frontend/rust-lib/flowy-document-infra/src/READ_ME.json
Normal file
1
frontend/rust-lib/flowy-document-infra/src/READ_ME.json
Normal file
@ -0,0 +1 @@
|
||||
[{"insert":"\n👋 Welcome to AppFlowy!"},{"insert":"\n","attributes":{"header":1}},{"insert":"\nHere are the basics"},{"insert":"\n","attributes":{"header":2}},{"insert":"Click anywhere and just start typing"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Highlight","attributes":{"background":"#fff2cd"}},{"insert":" any text, and use the menu at the bottom to "},{"insert":"style","attributes":{"italic":true}},{"insert":" "},{"insert":"your","attributes":{"bold":true}},{"insert":" "},{"insert":"writing","attributes":{"underline":true}},{"insert":" "},{"insert":"however","attributes":{"code":true}},{"insert":" "},{"insert":"you","attributes":{"strike":true}},{"insert":" "},{"insert":"like","attributes":{"background":"#e8e0ff"}},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Click "},{"insert":"+ New Page","attributes":{"background":"#defff1","bold":true}},{"insert":" button at the bottom of your sidebar to add a new page"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Click the "},{"insert":"'","attributes":{"background":"#defff1"}},{"insert":"+'","attributes":{"background":"#defff1","bold":true}},{"insert":" next to any page title in the sidebar to quickly add a new subpage"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"\nHave a question? "},{"insert":"\n","attributes":{"header":2}},{"insert":"Click the "},{"insert":"'?'","attributes":{"background":"#defff1","bold":true}},{"insert":" at the bottom right for help and support.\n\nLike AppFlowy? Follow us:"},{"insert":"\n","attributes":{"header":2}},{"insert":"Github: https://github.com/AppFlowy-IO/appflowy"},{"insert":"\n","attributes":{"blockquote":true}},{"insert":"Twitter: https://twitter.com/appflowy"},{"insert":"\n","attributes":{"blockquote":true}},{"insert":"Newsletter: https://www.appflowy.io/blog"},{"insert":"\n","attributes":{"blockquote":true}}]
|
10
frontend/rust-lib/flowy-document-infra/src/core/data.rs
Normal file
10
frontend/rust-lib/flowy-document-infra/src/core/data.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ImageData {
|
||||
image: String,
|
||||
}
|
||||
|
||||
impl ToString for ImageData {
|
||||
fn to_string(&self) -> String { self.image.clone() }
|
||||
}
|
213
frontend/rust-lib/flowy-document-infra/src/core/document.rs
Normal file
213
frontend/rust-lib/flowy-document-infra/src/core/document.rs
Normal file
@ -0,0 +1,213 @@
|
||||
use crate::{
|
||||
core::{
|
||||
history::{History, UndoResult},
|
||||
view::{View, RECORD_THRESHOLD},
|
||||
},
|
||||
errors::DocumentError,
|
||||
user_default::doc_initial_delta,
|
||||
};
|
||||
use lib_ot::core::*;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub trait CustomDocument {
|
||||
fn init_delta() -> Delta;
|
||||
}
|
||||
|
||||
pub struct PlainDoc();
|
||||
impl CustomDocument for PlainDoc {
|
||||
fn init_delta() -> Delta { Delta::new() }
|
||||
}
|
||||
|
||||
pub struct FlowyDoc();
|
||||
impl CustomDocument for FlowyDoc {
|
||||
fn init_delta() -> Delta { doc_initial_delta() }
|
||||
}
|
||||
|
||||
pub struct Document {
|
||||
delta: Delta,
|
||||
history: History,
|
||||
view: View,
|
||||
last_edit_time: usize,
|
||||
notify: Option<mpsc::UnboundedSender<()>>,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
pub fn new<C: CustomDocument>() -> Self { Self::from_delta(C::init_delta()) }
|
||||
|
||||
pub fn from_delta(delta: Delta) -> Self {
|
||||
Document {
|
||||
delta,
|
||||
history: History::new(),
|
||||
view: View::new(),
|
||||
last_edit_time: 0,
|
||||
notify: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json(json: &str) -> Result<Self, DocumentError> {
|
||||
let delta = Delta::from_json(json)?;
|
||||
Ok(Self::from_delta(delta))
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> String { self.delta.to_json() }
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> { self.delta.clone().to_bytes().to_vec() }
|
||||
|
||||
pub fn to_plain_string(&self) -> String { self.delta.apply("").unwrap() }
|
||||
|
||||
pub fn delta(&self) -> &Delta { &self.delta }
|
||||
|
||||
pub fn set_notify(&mut self, notify: mpsc::UnboundedSender<()>) { self.notify = Some(notify); }
|
||||
|
||||
pub fn set_delta(&mut self, data: Delta) {
|
||||
self.delta = data;
|
||||
|
||||
match &self.notify {
|
||||
None => {},
|
||||
Some(notify) => {
|
||||
let _ = notify.send(());
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compose_delta(&mut self, mut delta: Delta) -> Result<(), DocumentError> {
|
||||
trim(&mut delta);
|
||||
tracing::trace!("{} compose {}", &self.delta.to_json(), delta.to_json());
|
||||
let mut composed_delta = self.delta.compose(&delta)?;
|
||||
let mut undo_delta = delta.invert(&self.delta);
|
||||
|
||||
let now = chrono::Utc::now().timestamp_millis() as usize;
|
||||
if now - self.last_edit_time < RECORD_THRESHOLD {
|
||||
if let Some(last_delta) = self.history.undo() {
|
||||
tracing::trace!("compose previous change");
|
||||
tracing::trace!("current = {}", undo_delta);
|
||||
tracing::trace!("previous = {}", last_delta);
|
||||
undo_delta = undo_delta.compose(&last_delta)?;
|
||||
}
|
||||
} else {
|
||||
self.last_edit_time = now;
|
||||
}
|
||||
|
||||
tracing::trace!("👉 receive change undo: {}", undo_delta);
|
||||
if !undo_delta.is_empty() {
|
||||
self.history.record(undo_delta);
|
||||
}
|
||||
|
||||
tracing::trace!("compose result: {}", composed_delta.to_json());
|
||||
trim(&mut composed_delta);
|
||||
|
||||
self.set_delta(composed_delta);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn insert<T: ToString>(&mut self, index: usize, data: T) -> Result<Delta, DocumentError> {
|
||||
let interval = Interval::new(index, index);
|
||||
let _ = validate_interval(&self.delta, &interval)?;
|
||||
|
||||
let text = data.to_string();
|
||||
let delta = self.view.insert(&self.delta, &text, interval)?;
|
||||
tracing::trace!("👉 receive change: {}", delta);
|
||||
self.compose_delta(delta.clone())?;
|
||||
Ok(delta)
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, interval: Interval) -> Result<Delta, DocumentError> {
|
||||
let _ = validate_interval(&self.delta, &interval)?;
|
||||
debug_assert_eq!(interval.is_empty(), false);
|
||||
let delete = self.view.delete(&self.delta, interval)?;
|
||||
if !delete.is_empty() {
|
||||
tracing::trace!("👉 receive change: {}", delete);
|
||||
let _ = self.compose_delta(delete.clone())?;
|
||||
}
|
||||
Ok(delete)
|
||||
}
|
||||
|
||||
pub fn format(&mut self, interval: Interval, attribute: Attribute) -> Result<Delta, DocumentError> {
|
||||
let _ = validate_interval(&self.delta, &interval)?;
|
||||
tracing::trace!("format with {} at {}", attribute, interval);
|
||||
let format_delta = self.view.format(&self.delta, attribute.clone(), interval).unwrap();
|
||||
|
||||
tracing::trace!("👉 receive change: {}", format_delta);
|
||||
self.compose_delta(format_delta.clone())?;
|
||||
Ok(format_delta)
|
||||
}
|
||||
|
||||
pub fn replace<T: ToString>(&mut self, interval: Interval, data: T) -> Result<Delta, DocumentError> {
|
||||
let _ = validate_interval(&self.delta, &interval)?;
|
||||
let mut delta = Delta::default();
|
||||
let text = data.to_string();
|
||||
if !text.is_empty() {
|
||||
delta = self.view.insert(&self.delta, &text, interval)?;
|
||||
tracing::trace!("👉 receive change: {}", delta);
|
||||
self.compose_delta(delta.clone())?;
|
||||
}
|
||||
|
||||
if !interval.is_empty() {
|
||||
let delete = self.delete(interval)?;
|
||||
delta = delta.compose(&delete)?;
|
||||
}
|
||||
|
||||
Ok(delta)
|
||||
}
|
||||
|
||||
pub fn can_undo(&self) -> bool { self.history.can_undo() }
|
||||
|
||||
pub fn can_redo(&self) -> bool { self.history.can_redo() }
|
||||
|
||||
pub fn undo(&mut self) -> Result<UndoResult, DocumentError> {
|
||||
match self.history.undo() {
|
||||
None => Err(DocumentError::undo().context("Undo stack is empty")),
|
||||
Some(undo_delta) => {
|
||||
let (new_delta, inverted_delta) = self.invert(&undo_delta)?;
|
||||
let result = UndoResult::success(new_delta.target_len as usize);
|
||||
self.set_delta(new_delta);
|
||||
self.history.add_redo(inverted_delta);
|
||||
|
||||
Ok(result)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redo(&mut self) -> Result<UndoResult, DocumentError> {
|
||||
match self.history.redo() {
|
||||
None => Err(DocumentError::redo()),
|
||||
Some(redo_delta) => {
|
||||
let (new_delta, inverted_delta) = self.invert(&redo_delta)?;
|
||||
let result = UndoResult::success(new_delta.target_len as usize);
|
||||
self.set_delta(new_delta);
|
||||
|
||||
self.history.add_undo(inverted_delta);
|
||||
Ok(result)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Document {
|
||||
fn invert(&self, delta: &Delta) -> Result<(Delta, Delta), DocumentError> {
|
||||
// c = a.compose(b)
|
||||
// d = b.invert(a)
|
||||
// a = c.compose(d)
|
||||
tracing::trace!("Invert {}", delta);
|
||||
let new_delta = self.delta.compose(delta)?;
|
||||
let inverted_delta = delta.invert(&self.delta);
|
||||
Ok((new_delta, inverted_delta))
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_interval(delta: &Delta, interval: &Interval) -> Result<(), DocumentError> {
|
||||
if delta.target_len < interval.end {
|
||||
log::error!("{:?} out of bounds. should 0..{}", interval, delta.target_len);
|
||||
return Err(DocumentError::out_of_bound());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes trailing retain operation with empty attributes, if present.
|
||||
pub fn trim(delta: &mut Delta) {
|
||||
if let Some(last) = delta.ops.last() {
|
||||
if last.is_retain() && last.is_plain() {
|
||||
delta.ops.pop();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
use crate::core::extensions::DeleteExt;
|
||||
use lib_ot::core::{Delta, DeltaBuilder, Interval};
|
||||
|
||||
pub struct DefaultDelete {}
|
||||
impl DeleteExt for DefaultDelete {
|
||||
fn ext_name(&self) -> &str { "DefaultDelete" }
|
||||
|
||||
fn apply(&self, _delta: &Delta, interval: Interval) -> Option<Delta> {
|
||||
Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(interval.start)
|
||||
.delete(interval.size())
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
mod default_delete;
|
||||
mod preserve_line_format_merge;
|
||||
|
||||
pub use default_delete::*;
|
||||
pub use preserve_line_format_merge::*;
|
@ -0,0 +1,57 @@
|
||||
use crate::{core::extensions::DeleteExt, util::is_newline};
|
||||
use lib_ot::core::{plain_attributes, CharMetric, Delta, DeltaBuilder, DeltaIter, Interval, NEW_LINE};
|
||||
|
||||
pub struct PreserveLineFormatOnMerge {}
|
||||
impl DeleteExt for PreserveLineFormatOnMerge {
|
||||
fn ext_name(&self) -> &str { "PreserveLineFormatOnMerge" }
|
||||
|
||||
fn apply(&self, delta: &Delta, interval: Interval) -> Option<Delta> {
|
||||
if interval.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// seek to the interval start pos. e.g. You backspace enter pos
|
||||
let mut iter = DeltaIter::from_offset(delta, interval.start);
|
||||
|
||||
// op will be the "\n"
|
||||
let newline_op = iter.next_op_with_len(1)?;
|
||||
if !is_newline(newline_op.get_data()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
iter.seek::<CharMetric>(interval.size() - 1);
|
||||
let mut new_delta = DeltaBuilder::new()
|
||||
.retain(interval.start)
|
||||
.delete(interval.size())
|
||||
.build();
|
||||
|
||||
while iter.has_next() {
|
||||
match iter.next() {
|
||||
None => log::error!("op must be not None when has_next() return true"),
|
||||
Some(op) => {
|
||||
//
|
||||
match op.get_data().find(NEW_LINE) {
|
||||
None => {
|
||||
new_delta.retain(op.len(), plain_attributes());
|
||||
continue;
|
||||
},
|
||||
Some(line_break) => {
|
||||
let mut attributes = op.get_attributes();
|
||||
attributes.mark_all_as_removed_except(None);
|
||||
|
||||
if newline_op.has_attribute() {
|
||||
attributes.extend(newline_op.get_attributes());
|
||||
}
|
||||
|
||||
new_delta.retain(line_break, plain_attributes());
|
||||
new_delta.retain(1, attributes);
|
||||
break;
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Some(new_delta)
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
// use crate::{
|
||||
// client::extensions::FormatExt,
|
||||
// core::{Attribute, AttributeKey, Delta, DeltaBuilder, DeltaIter,
|
||||
// Interval}, };
|
||||
//
|
||||
// pub struct FormatLinkAtCaretPositionExt {}
|
||||
//
|
||||
// impl FormatExt for FormatLinkAtCaretPositionExt {
|
||||
// fn ext_name(&self) -> &str {
|
||||
// std::any::type_name::<FormatLinkAtCaretPositionExt>() }
|
||||
//
|
||||
// fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute)
|
||||
// -> Option<Delta> { if attribute.key != AttributeKey::Link ||
|
||||
// interval.size() != 0 { return None;
|
||||
// }
|
||||
//
|
||||
// let mut iter = DeltaIter::from_offset(delta, interval.start);
|
||||
// let (before, after) = (iter.next_op_with_len(interval.size()),
|
||||
// iter.next_op()); let mut start = interval.end;
|
||||
// let mut retain = 0;
|
||||
//
|
||||
// if let Some(before) = before {
|
||||
// if before.contain_attribute(attribute) {
|
||||
// start -= before.len();
|
||||
// retain += before.len();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if let Some(after) = after {
|
||||
// if after.contain_attribute(attribute) {
|
||||
// if retain != 0 {
|
||||
// retain += after.len();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if retain == 0 {
|
||||
// return None;
|
||||
// }
|
||||
//
|
||||
// Some(
|
||||
// DeltaBuilder::new()
|
||||
// .retain(start)
|
||||
// .retain_with_attributes(retain, (attribute.clone()).into())
|
||||
// .build(),
|
||||
// )
|
||||
// }
|
||||
// }
|
@ -0,0 +1,37 @@
|
||||
use crate::util::find_newline;
|
||||
use lib_ot::core::{plain_attributes, Attribute, AttributeScope, Delta, Operation};
|
||||
|
||||
pub(crate) fn line_break(op: &Operation, attribute: &Attribute, scope: AttributeScope) -> Delta {
|
||||
let mut new_delta = Delta::new();
|
||||
let mut start = 0;
|
||||
let end = op.len();
|
||||
let mut s = op.get_data();
|
||||
|
||||
while let Some(line_break) = find_newline(s) {
|
||||
match scope {
|
||||
AttributeScope::Inline => {
|
||||
new_delta.retain(line_break - start, attribute.clone().into());
|
||||
new_delta.retain(1, plain_attributes());
|
||||
},
|
||||
AttributeScope::Block => {
|
||||
new_delta.retain(line_break - start, plain_attributes());
|
||||
new_delta.retain(1, attribute.clone().into());
|
||||
},
|
||||
_ => {
|
||||
log::error!("Unsupported parser line break for {:?}", scope);
|
||||
},
|
||||
}
|
||||
|
||||
start = line_break + 1;
|
||||
s = &s[start..s.len()];
|
||||
}
|
||||
|
||||
if start < end {
|
||||
match scope {
|
||||
AttributeScope::Inline => new_delta.retain(end - start, attribute.clone().into()),
|
||||
AttributeScope::Block => new_delta.retain(end - start, plain_attributes()),
|
||||
_ => log::error!("Unsupported parser line break for {:?}", scope),
|
||||
}
|
||||
}
|
||||
new_delta
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
mod format_at_position;
|
||||
mod helper;
|
||||
mod resolve_block_format;
|
||||
mod resolve_inline_format;
|
||||
|
||||
pub use format_at_position::*;
|
||||
pub use resolve_block_format::*;
|
||||
pub use resolve_inline_format::*;
|
@ -0,0 +1,48 @@
|
||||
use crate::{
|
||||
core::extensions::{format::helper::line_break, FormatExt},
|
||||
util::find_newline,
|
||||
};
|
||||
use lib_ot::core::{plain_attributes, Attribute, AttributeScope, Delta, DeltaBuilder, DeltaIter, Interval};
|
||||
|
||||
pub struct ResolveBlockFormat {}
|
||||
impl FormatExt for ResolveBlockFormat {
|
||||
fn ext_name(&self) -> &str { std::any::type_name::<ResolveBlockFormat>() }
|
||||
|
||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||
if attribute.scope != AttributeScope::Block {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
|
||||
let mut iter = DeltaIter::from_offset(delta, interval.start);
|
||||
let mut start = 0;
|
||||
let end = interval.size();
|
||||
while start < end && iter.has_next() {
|
||||
let next_op = iter.next_op_with_len(end - start).unwrap();
|
||||
match find_newline(next_op.get_data()) {
|
||||
None => new_delta.retain(next_op.len(), plain_attributes()),
|
||||
Some(_) => {
|
||||
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Block);
|
||||
new_delta.extend(tmp_delta);
|
||||
},
|
||||
}
|
||||
|
||||
start += next_op.len();
|
||||
}
|
||||
|
||||
while iter.has_next() {
|
||||
let op = iter.next_op().expect("Unexpected None, iter.has_next() must return op");
|
||||
|
||||
match find_newline(op.get_data()) {
|
||||
None => new_delta.retain(op.len(), plain_attributes()),
|
||||
Some(line_break) => {
|
||||
new_delta.retain(line_break, plain_attributes());
|
||||
new_delta.retain(1, attribute.clone().into());
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Some(new_delta)
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
use crate::{
|
||||
core::extensions::{format::helper::line_break, FormatExt},
|
||||
util::find_newline,
|
||||
};
|
||||
use lib_ot::core::{Attribute, AttributeScope, Delta, DeltaBuilder, DeltaIter, Interval};
|
||||
|
||||
pub struct ResolveInlineFormat {}
|
||||
impl FormatExt for ResolveInlineFormat {
|
||||
fn ext_name(&self) -> &str { std::any::type_name::<ResolveInlineFormat>() }
|
||||
|
||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||
if attribute.scope != AttributeScope::Inline {
|
||||
return None;
|
||||
}
|
||||
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
|
||||
let mut iter = DeltaIter::from_offset(delta, interval.start);
|
||||
let mut start = 0;
|
||||
let end = interval.size();
|
||||
|
||||
while start < end && iter.has_next() {
|
||||
let next_op = iter.next_op_with_len(end - start).unwrap();
|
||||
match find_newline(next_op.get_data()) {
|
||||
None => new_delta.retain(next_op.len(), attribute.clone().into()),
|
||||
Some(_) => {
|
||||
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Inline);
|
||||
new_delta.extend(tmp_delta);
|
||||
},
|
||||
}
|
||||
|
||||
start += next_op.len();
|
||||
}
|
||||
|
||||
Some(new_delta)
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
use crate::{core::extensions::InsertExt, util::is_newline};
|
||||
use lib_ot::core::{attributes_except_header, is_empty_line_at_index, AttributeKey, Delta, DeltaBuilder, DeltaIter};
|
||||
|
||||
pub struct AutoExitBlock {}
|
||||
|
||||
impl InsertExt for AutoExitBlock {
|
||||
fn ext_name(&self) -> &str { std::any::type_name::<AutoExitBlock>() }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
// Auto exit block will be triggered by enter two new lines
|
||||
if !is_newline(text) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !is_empty_line_at_index(delta, index) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::from_offset(delta, index);
|
||||
let next = iter.next_op()?;
|
||||
let mut attributes = next.get_attributes();
|
||||
|
||||
let block_attributes = attributes_except_header(&next);
|
||||
if block_attributes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if next.len() > 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
match iter.next_op_with_newline() {
|
||||
None => {},
|
||||
Some((newline_op, _)) => {
|
||||
let newline_attributes = attributes_except_header(&newline_op);
|
||||
if block_attributes == newline_attributes {
|
||||
return None;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
attributes.mark_all_as_removed_except(Some(AttributeKey::Header));
|
||||
|
||||
Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(index + replace_len)
|
||||
.retain_with_attributes(1, attributes)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
use crate::{core::extensions::InsertExt, util::is_whitespace};
|
||||
use lib_ot::core::{count_utf16_code_units, plain_attributes, Attribute, Attributes, Delta, DeltaBuilder, DeltaIter};
|
||||
use std::cmp::min;
|
||||
use url::Url;
|
||||
|
||||
pub struct AutoFormatExt {}
|
||||
impl InsertExt for AutoFormatExt {
|
||||
fn ext_name(&self) -> &str { std::any::type_name::<AutoFormatExt>() }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
// enter whitespace to trigger auto format
|
||||
if !is_whitespace(text) {
|
||||
return None;
|
||||
}
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
if let Some(prev) = iter.next_op_with_len(index) {
|
||||
match AutoFormat::parse(prev.get_data()) {
|
||||
None => {},
|
||||
Some(formatter) => {
|
||||
let mut new_attributes = prev.get_attributes();
|
||||
|
||||
// format_len should not greater than index. The url crate will add "/" to the
|
||||
// end of input string that causes the format_len greater than the input string
|
||||
let format_len = min(index, formatter.format_len());
|
||||
|
||||
let format_attributes = formatter.to_attributes();
|
||||
format_attributes.iter().for_each(|(k, v)| {
|
||||
if !new_attributes.contains_key(k) {
|
||||
new_attributes.insert(k.clone(), v.clone());
|
||||
}
|
||||
});
|
||||
|
||||
let next_attributes = match iter.next_op() {
|
||||
None => plain_attributes(),
|
||||
Some(op) => op.get_attributes(),
|
||||
};
|
||||
|
||||
return Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(index + replace_len - min(index, format_len))
|
||||
.retain_with_attributes(format_len, format_attributes)
|
||||
.insert_with_attributes(text, next_attributes)
|
||||
.build(),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AutoFormatter {
|
||||
Url(Url),
|
||||
}
|
||||
|
||||
impl AutoFormatter {
|
||||
pub fn to_attributes(&self) -> Attributes {
|
||||
match self {
|
||||
AutoFormatter::Url(url) => Attribute::Link(url.as_str()).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_len(&self) -> usize {
|
||||
let s = match self {
|
||||
AutoFormatter::Url(url) => url.to_string(),
|
||||
};
|
||||
|
||||
count_utf16_code_units(&s)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AutoFormat {}
|
||||
impl AutoFormat {
|
||||
fn parse(s: &str) -> Option<AutoFormatter> {
|
||||
if let Ok(url) = Url::parse(s) {
|
||||
return Some(AutoFormatter::Url(url));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
use crate::core::extensions::InsertExt;
|
||||
use lib_ot::core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter, NEW_LINE};
|
||||
|
||||
pub struct DefaultInsertAttribute {}
|
||||
impl InsertExt for DefaultInsertAttribute {
|
||||
fn ext_name(&self) -> &str { std::any::type_name::<DefaultInsertAttribute>() }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
let iter = DeltaIter::new(delta);
|
||||
let mut attributes = Attributes::new();
|
||||
|
||||
// Enable each line split by "\n" remains the block attributes. for example:
|
||||
// insert "\n" to "123456" at index 3
|
||||
//
|
||||
// [{"insert":"123"},{"insert":"\n","attributes":{"header":1}},
|
||||
// {"insert":"456"},{"insert":"\n","attributes":{"header":1}}]
|
||||
if text.ends_with(NEW_LINE) {
|
||||
match iter.last() {
|
||||
None => {},
|
||||
Some(op) => {
|
||||
if op.get_attributes().contains_key(&AttributeKey::Header) {
|
||||
attributes.extend(op.get_attributes());
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(index + replace_len)
|
||||
.insert_with_attributes(text, attributes)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
use crate::core::extensions::InsertExt;
|
||||
pub use auto_exit_block::*;
|
||||
pub use auto_format::*;
|
||||
pub use default_insert::*;
|
||||
use lib_ot::core::Delta;
|
||||
pub use preserve_block_format::*;
|
||||
pub use preserve_inline_format::*;
|
||||
pub use reset_format_on_new_line::*;
|
||||
|
||||
mod auto_exit_block;
|
||||
mod auto_format;
|
||||
mod default_insert;
|
||||
mod preserve_block_format;
|
||||
mod preserve_inline_format;
|
||||
mod reset_format_on_new_line;
|
||||
|
||||
pub struct InsertEmbedsExt {}
|
||||
impl InsertExt for InsertEmbedsExt {
|
||||
fn ext_name(&self) -> &str { "InsertEmbedsExt" }
|
||||
|
||||
fn apply(&self, _delta: &Delta, _replace_len: usize, _text: &str, _index: usize) -> Option<Delta> { None }
|
||||
}
|
||||
|
||||
pub struct ForceNewlineForInsertsAroundEmbedExt {}
|
||||
impl InsertExt for ForceNewlineForInsertsAroundEmbedExt {
|
||||
fn ext_name(&self) -> &str { "ForceNewlineForInsertsAroundEmbedExt" }
|
||||
|
||||
fn apply(&self, _delta: &Delta, _replace_len: usize, _text: &str, _index: usize) -> Option<Delta> { None }
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
use crate::{core::extensions::InsertExt, util::is_newline};
|
||||
use lib_ot::core::{
|
||||
attributes_except_header,
|
||||
plain_attributes,
|
||||
Attribute,
|
||||
AttributeKey,
|
||||
Attributes,
|
||||
Delta,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
NEW_LINE,
|
||||
};
|
||||
|
||||
pub struct PreserveBlockFormatOnInsert {}
|
||||
impl InsertExt for PreserveBlockFormatOnInsert {
|
||||
fn ext_name(&self) -> &str { std::any::type_name::<PreserveBlockFormatOnInsert>() }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
if !is_newline(text) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::from_offset(delta, index);
|
||||
match iter.next_op_with_newline() {
|
||||
None => {},
|
||||
Some((newline_op, offset)) => {
|
||||
let newline_attributes = newline_op.get_attributes();
|
||||
let block_attributes = attributes_except_header(&newline_op);
|
||||
if block_attributes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut reset_attribute = Attributes::new();
|
||||
if newline_attributes.contains_key(&AttributeKey::Header) {
|
||||
reset_attribute.add(Attribute::Header(1));
|
||||
}
|
||||
|
||||
let lines: Vec<_> = text.split(NEW_LINE).collect();
|
||||
let mut new_delta = DeltaBuilder::new().retain(index + replace_len).build();
|
||||
lines.iter().enumerate().for_each(|(i, line)| {
|
||||
if !line.is_empty() {
|
||||
new_delta.insert(line, plain_attributes());
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
new_delta.insert(NEW_LINE, newline_attributes.clone());
|
||||
} else if i < lines.len() - 1 {
|
||||
new_delta.insert(NEW_LINE, block_attributes.clone());
|
||||
} else {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
if !reset_attribute.is_empty() {
|
||||
new_delta.retain(offset, plain_attributes());
|
||||
let len = newline_op.get_data().find(NEW_LINE).unwrap();
|
||||
new_delta.retain(len, plain_attributes());
|
||||
new_delta.retain(1, reset_attribute.clone());
|
||||
}
|
||||
|
||||
return Some(new_delta);
|
||||
},
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
use crate::{
|
||||
core::extensions::InsertExt,
|
||||
util::{contain_newline, is_newline},
|
||||
};
|
||||
use lib_ot::core::{plain_attributes, AttributeKey, Delta, DeltaBuilder, DeltaIter, OpNewline, NEW_LINE};
|
||||
|
||||
pub struct PreserveInlineFormat {}
|
||||
impl InsertExt for PreserveInlineFormat {
|
||||
fn ext_name(&self) -> &str { std::any::type_name::<PreserveInlineFormat>() }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
if contain_newline(text) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
let prev = iter.next_op_with_len(index)?;
|
||||
if OpNewline::parse(&prev).is_contain() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut attributes = prev.get_attributes();
|
||||
if attributes.is_empty() || !attributes.contains_key(&AttributeKey::Link) {
|
||||
return Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(index + replace_len)
|
||||
.insert_with_attributes(text, attributes)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
let next = iter.next_op();
|
||||
match &next {
|
||||
None => attributes = plain_attributes(),
|
||||
Some(next) => {
|
||||
if OpNewline::parse(&next).is_equal() {
|
||||
attributes = plain_attributes();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
let new_delta = DeltaBuilder::new()
|
||||
.retain(index + replace_len)
|
||||
.insert_with_attributes(text, attributes)
|
||||
.build();
|
||||
|
||||
return Some(new_delta);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PreserveLineFormatOnSplit {}
|
||||
impl InsertExt for PreserveLineFormatOnSplit {
|
||||
fn ext_name(&self) -> &str { std::any::type_name::<PreserveLineFormatOnSplit>() }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
if !is_newline(text) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
let prev = iter.next_op_with_len(index)?;
|
||||
if OpNewline::parse(&prev).is_end() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let next = iter.next_op()?;
|
||||
let newline_status = OpNewline::parse(&next);
|
||||
if newline_status.is_end() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut new_delta = Delta::new();
|
||||
new_delta.retain(index + replace_len, plain_attributes());
|
||||
|
||||
if newline_status.is_contain() {
|
||||
debug_assert!(next.has_attribute() == false);
|
||||
new_delta.insert(NEW_LINE, plain_attributes());
|
||||
return Some(new_delta);
|
||||
}
|
||||
|
||||
match iter.next_op_with_newline() {
|
||||
None => {},
|
||||
Some((newline_op, _)) => {
|
||||
new_delta.insert(NEW_LINE, newline_op.get_attributes());
|
||||
},
|
||||
}
|
||||
|
||||
Some(new_delta)
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
use crate::{core::extensions::InsertExt, util::is_newline};
|
||||
use lib_ot::core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter, NEW_LINE};
|
||||
|
||||
pub struct ResetLineFormatOnNewLine {}
|
||||
impl InsertExt for ResetLineFormatOnNewLine {
|
||||
fn ext_name(&self) -> &str { std::any::type_name::<ResetLineFormatOnNewLine>() }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
if !is_newline(text) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(index);
|
||||
let next_op = iter.next_op()?;
|
||||
if !next_op.get_data().starts_with(NEW_LINE) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut reset_attribute = Attributes::new();
|
||||
if next_op.get_attributes().contains_key(&AttributeKey::Header) {
|
||||
reset_attribute.delete(&AttributeKey::Header);
|
||||
}
|
||||
|
||||
let len = index + replace_len;
|
||||
Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(len)
|
||||
.insert_with_attributes(NEW_LINE, next_op.get_attributes())
|
||||
.retain_with_attributes(1, reset_attribute)
|
||||
.trim()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
pub use delete::*;
|
||||
pub use format::*;
|
||||
pub use insert::*;
|
||||
|
||||
use lib_ot::core::{Attribute, Delta, Interval};
|
||||
|
||||
mod delete;
|
||||
mod format;
|
||||
mod insert;
|
||||
|
||||
pub type InsertExtension = Box<dyn InsertExt + Send + Sync>;
|
||||
pub type FormatExtension = Box<dyn FormatExt + Send + Sync>;
|
||||
pub type DeleteExtension = Box<dyn DeleteExt + Send + Sync>;
|
||||
|
||||
pub trait InsertExt {
|
||||
fn ext_name(&self) -> &str;
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta>;
|
||||
}
|
||||
|
||||
pub trait FormatExt {
|
||||
fn ext_name(&self) -> &str;
|
||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta>;
|
||||
}
|
||||
|
||||
pub trait DeleteExt {
|
||||
fn ext_name(&self) -> &str;
|
||||
fn apply(&self, delta: &Delta, interval: Interval) -> Option<Delta>;
|
||||
}
|
72
frontend/rust-lib/flowy-document-infra/src/core/history.rs
Normal file
72
frontend/rust-lib/flowy-document-infra/src/core/history.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use lib_ot::core::Delta;
|
||||
|
||||
const MAX_UNDOS: usize = 20;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UndoResult {
|
||||
success: bool,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl UndoResult {
|
||||
pub fn fail() -> Self { UndoResult { success: false, len: 0 } }
|
||||
|
||||
pub fn success(len: usize) -> Self { UndoResult { success: true, len } }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct History {
|
||||
cur_undo: usize,
|
||||
undos: Vec<Delta>,
|
||||
redoes: Vec<Delta>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl History {
|
||||
pub fn new() -> Self {
|
||||
History {
|
||||
cur_undo: 1,
|
||||
undos: Vec::new(),
|
||||
redoes: Vec::new(),
|
||||
capacity: MAX_UNDOS,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_undo(&self) -> bool { !self.undos.is_empty() }
|
||||
|
||||
pub fn can_redo(&self) -> bool { !self.redoes.is_empty() }
|
||||
|
||||
pub fn add_undo(&mut self, delta: Delta) { self.undos.push(delta); }
|
||||
|
||||
pub fn add_redo(&mut self, delta: Delta) { self.redoes.push(delta); }
|
||||
|
||||
pub fn record(&mut self, delta: Delta) {
|
||||
if delta.ops.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.redoes.clear();
|
||||
self.add_undo(delta);
|
||||
|
||||
if self.undos.len() > self.capacity {
|
||||
self.undos.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undo(&mut self) -> Option<Delta> {
|
||||
if !self.can_undo() {
|
||||
return None;
|
||||
}
|
||||
let delta = self.undos.pop().unwrap();
|
||||
Some(delta)
|
||||
}
|
||||
|
||||
pub fn redo(&mut self) -> Option<Delta> {
|
||||
if !self.can_redo() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let delta = self.redoes.pop().unwrap();
|
||||
Some(delta)
|
||||
}
|
||||
}
|
8
frontend/rust-lib/flowy-document-infra/src/core/mod.rs
Normal file
8
frontend/rust-lib/flowy-document-infra/src/core/mod.rs
Normal file
@ -0,0 +1,8 @@
|
||||
mod data;
|
||||
mod document;
|
||||
mod extensions;
|
||||
pub mod history;
|
||||
mod view;
|
||||
|
||||
pub use document::*;
|
||||
pub use view::RECORD_THRESHOLD;
|
100
frontend/rust-lib/flowy-document-infra/src/core/view.rs
Normal file
100
frontend/rust-lib/flowy-document-infra/src/core/view.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use crate::core::extensions::*;
|
||||
use lib_ot::{
|
||||
core::{trim, Attribute, Delta, Interval},
|
||||
errors::{ErrorBuilder, OTError, OTErrorCode},
|
||||
};
|
||||
|
||||
pub const RECORD_THRESHOLD: usize = 400; // in milliseconds
|
||||
|
||||
pub struct View {
|
||||
insert_exts: Vec<InsertExtension>,
|
||||
format_exts: Vec<FormatExtension>,
|
||||
delete_exts: Vec<DeleteExtension>,
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
insert_exts: construct_insert_exts(),
|
||||
format_exts: construct_format_exts(),
|
||||
delete_exts: construct_delete_exts(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&self, delta: &Delta, text: &str, interval: Interval) -> Result<Delta, OTError> {
|
||||
let mut new_delta = None;
|
||||
for ext in &self.insert_exts {
|
||||
if let Some(mut delta) = ext.apply(delta, interval.size(), text, interval.start) {
|
||||
trim(&mut delta);
|
||||
tracing::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta);
|
||||
new_delta = Some(delta);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match new_delta {
|
||||
None => Err(ErrorBuilder::new(OTErrorCode::ApplyInsertFail).build()),
|
||||
Some(new_delta) => Ok(new_delta),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn delete(&self, delta: &Delta, interval: Interval) -> Result<Delta, OTError> {
|
||||
let mut new_delta = None;
|
||||
for ext in &self.delete_exts {
|
||||
if let Some(mut delta) = ext.apply(delta, interval) {
|
||||
trim(&mut delta);
|
||||
tracing::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta);
|
||||
new_delta = Some(delta);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match new_delta {
|
||||
None => Err(ErrorBuilder::new(OTErrorCode::ApplyDeleteFail).build()),
|
||||
Some(new_delta) => Ok(new_delta),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn format(&self, delta: &Delta, attribute: Attribute, interval: Interval) -> Result<Delta, OTError> {
|
||||
let mut new_delta = None;
|
||||
for ext in &self.format_exts {
|
||||
if let Some(mut delta) = ext.apply(delta, interval, &attribute) {
|
||||
trim(&mut delta);
|
||||
tracing::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta);
|
||||
new_delta = Some(delta);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match new_delta {
|
||||
None => Err(ErrorBuilder::new(OTErrorCode::ApplyFormatFail).build()),
|
||||
Some(new_delta) => Ok(new_delta),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn construct_insert_exts() -> Vec<InsertExtension> {
|
||||
vec![
|
||||
Box::new(InsertEmbedsExt {}),
|
||||
Box::new(ForceNewlineForInsertsAroundEmbedExt {}),
|
||||
Box::new(AutoExitBlock {}),
|
||||
Box::new(PreserveBlockFormatOnInsert {}),
|
||||
Box::new(PreserveLineFormatOnSplit {}),
|
||||
Box::new(ResetLineFormatOnNewLine {}),
|
||||
Box::new(AutoFormatExt {}),
|
||||
Box::new(PreserveInlineFormat {}),
|
||||
Box::new(DefaultInsertAttribute {}),
|
||||
]
|
||||
}
|
||||
|
||||
fn construct_format_exts() -> Vec<FormatExtension> {
|
||||
vec![
|
||||
// Box::new(FormatLinkAtCaretPositionExt {}),
|
||||
Box::new(ResolveBlockFormat {}),
|
||||
Box::new(ResolveInlineFormat {}),
|
||||
]
|
||||
}
|
||||
|
||||
fn construct_delete_exts() -> Vec<DeleteExtension> {
|
||||
vec![Box::new(PreserveLineFormatOnMerge {}), Box::new(DefaultDelete {})]
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user