add frontend folder

This commit is contained in:
appflowy
2021-11-20 09:32:46 +08:00
parent f93f012bc8
commit 8f1d62f115
1697 changed files with 754 additions and 104 deletions

View 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
View 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/

View 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"

View 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"]

View 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) }
}

View 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,
}

View 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;

View 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),
},
}
}
}
}
}

View File

@ -0,0 +1,3 @@
mod request;
pub use request::*;

View 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()
},
}
}

View File

@ -0,0 +1,6 @@
mod response;
#[cfg(feature = "http_server")]
mod response_http;
pub use response::*;

View 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) }
}

View File

@ -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,
}
}
}

View 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(())
}

View 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)
}

View 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"]

View File

@ -0,0 +1,2 @@
proto_crates = ["src/model"]
event_files = []

View 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);

View 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
}

View 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");
}
},
}
}

View 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) }
}

View 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 }
}
}

View File

@ -0,0 +1,5 @@
mod ffi_request;
mod ffi_response;
pub use ffi_request::*;
pub use ffi_response::*;

View File

@ -0,0 +1,4 @@
mod model;
pub use model::*;

View 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()
})
}

View 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()
})
}

View File

@ -0,0 +1,7 @@
// Auto-generated, do not edit
mod ffi_response;
pub use ffi_response::*;
mod ffi_request;
pub use ffi_request::*;

View File

@ -0,0 +1,6 @@
syntax = "proto3";
message FFIRequest {
string event = 1;
bytes payload = 2;
}

View File

@ -0,0 +1,11 @@
syntax = "proto3";
message FFIResponse {
bytes payload = 1;
FFIStatusCode code = 2;
}
enum FFIStatusCode {
Ok = 0;
Err = 1;
Internal = 2;
}

View File

@ -0,0 +1 @@

View 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 = []

View File

@ -0,0 +1,3 @@
proto_crates = ["src/entities"]
event_files = []

View File

@ -0,0 +1,3 @@
mod stream_sender;
pub use stream_sender::*;

View 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(())
}
}

View File

@ -0,0 +1,3 @@
mod subject;
pub use subject::*;

View 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,
}
}
}

View 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),
}
}
}

View File

@ -0,0 +1,4 @@
mod model;
pub use model::*;

View File

@ -0,0 +1,4 @@
// Auto-generated, do not edit
mod subject;
pub use subject::*;

View 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()
})
}

View File

@ -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; };
}

View 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"

View 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()
}

View 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() }
}

View 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");
}
}
}

View 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,
}
}
}

View 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;

View 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) }
}

View 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;
}

View File

@ -0,0 +1 @@
DATABASE_URL=/tmp/database.sql

View 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" }

View 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"

View File

@ -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;

View File

@ -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
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE user_table;

View File

@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE user_table ADD COLUMN workspace TEXT NOT NULL DEFAULT '';

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE doc_table;

View File

@ -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
);

View File

@ -0,0 +1 @@
-- This file should undo anything in `up.sql`

View File

@ -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
);

View 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>;
}

View 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))
}
}
};
}

View 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,
);

View File

@ -0,0 +1,2 @@
/target
Cargo.lock

View 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"

View 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)*
// }
// }
// }
// })
// }

View File

@ -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,
}
}

View File

@ -0,0 +1,3 @@
mod derive_cache;
pub use derive_cache::*;

View 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)*)
}

View 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;
}),
}
}

View 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)*
})
}
}
})
}

View 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);

View 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;
})
},
}
}

View 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"));
}
}

View File

@ -0,0 +1,5 @@
#[tokio::test]
async fn tests() {
let _t = trybuild::TestCases::new();
// t.pass("tests/protobuf_enum.rs");
}

View 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"

View File

@ -0,0 +1,3 @@
proto_crates = ["src/entities"]
event_files = []

View 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}}]

View 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() }
}

View 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();
}
}
}

View File

@ -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(),
)
}
}

View File

@ -0,0 +1,5 @@
mod default_delete;
mod preserve_line_format_merge;
pub use default_delete::*;
pub use preserve_line_format_merge::*;

View File

@ -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)
}
}

View File

@ -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(),
// )
// }
// }

View File

@ -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
}

View File

@ -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::*;

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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(),
)
}
}

View File

@ -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
}
}

View File

@ -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(),
)
}
}

View File

@ -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 }
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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(),
)
}
}

View File

@ -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>;
}

View 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)
}
}

View File

@ -0,0 +1,8 @@
mod data;
mod document;
mod extensions;
pub mod history;
mod view;
pub use document::*;
pub use view::RECORD_THRESHOLD;

View 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