mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add backend kv store
This commit is contained in:
parent
da4f7f40e7
commit
eb601932ea
@ -5,14 +5,7 @@
|
||||
|
||||
1. follow the [instructions](https://docs.docker.com/desktop/mac/install/) to install docker.
|
||||
2. open terminal and run: `docker pull postgres`
|
||||
|
||||
3. run `make init_postgres` if you have not run before. You can find out the running container by run `docker ps`
|
||||
```
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
bfcdd6369e89 postgres "docker-entrypoint.s…" 19 minutes ago Up 19 minutes 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp brave_bassi
|
||||
```
|
||||
|
||||
4. run `make init_database`. It will create the database scheme on remote specified by DATABASE_URL. You can connect you database using
|
||||
3run `make init_database`. It will create the database scheme on remote specified by DATABASE_URL. You can connect you database using
|
||||
pgAdmin.
|
||||
|
||||
![img_2.png](img_2.png)
|
||||
|
6
backend/migrations/20211221061753_kv.sql
Normal file
6
backend/migrations/20211221061753_kv.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- Add migration script here
|
||||
CREATE TABLE IF NOT EXISTS kv_table(
|
||||
id TEXT NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
blob bytea
|
||||
);
|
@ -1,5 +1,6 @@
|
||||
use crate::services::{
|
||||
document::manager::DocumentManager,
|
||||
kv_store::{KVStore, PostgresKV},
|
||||
web_socket::{WSServer, WebSocketReceivers},
|
||||
};
|
||||
use actix::Addr;
|
||||
@ -14,6 +15,7 @@ pub struct AppContext {
|
||||
pub pg_pool: Data<PgPool>,
|
||||
pub ws_receivers: Data<WebSocketReceivers>,
|
||||
pub document_mng: Data<Arc<DocumentManager>>,
|
||||
pub kv_store: Data<Arc<dyn KVStore>>,
|
||||
}
|
||||
|
||||
impl AppContext {
|
||||
@ -24,12 +26,16 @@ impl AppContext {
|
||||
let mut ws_receivers = WebSocketReceivers::new();
|
||||
let document_mng = Arc::new(DocumentManager::new(pg_pool.clone()));
|
||||
ws_receivers.set(WSModule::Doc, document_mng.clone());
|
||||
let kv_store = Arc::new(PostgresKV {
|
||||
pg_pool: pg_pool.clone(),
|
||||
});
|
||||
|
||||
AppContext {
|
||||
ws_server,
|
||||
pg_pool,
|
||||
ws_receivers: Data::new(ws_receivers),
|
||||
document_mng: Data::new(document_mng),
|
||||
kv_store: Data::new(kv_store),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,14 +80,14 @@ impl NewAppSqlBuilder {
|
||||
let app: App = self.table.clone().into();
|
||||
|
||||
let (sql, args) = SqlBuilder::create(APP_TABLE)
|
||||
.add_arg("id", self.table.id)
|
||||
.add_arg("workspace_id", self.table.workspace_id)
|
||||
.add_arg("name", self.table.name)
|
||||
.add_arg("description", self.table.description)
|
||||
.add_arg("color_style", self.table.color_style)
|
||||
.add_arg("modified_time", self.table.modified_time)
|
||||
.add_arg("create_time", self.table.create_time)
|
||||
.add_arg("user_id", self.table.user_id)
|
||||
.add_field_with_arg("id", self.table.id)
|
||||
.add_field_with_arg("workspace_id", self.table.workspace_id)
|
||||
.add_field_with_arg("name", self.table.name)
|
||||
.add_field_with_arg("description", self.table.description)
|
||||
.add_field_with_arg("color_style", self.table.color_style)
|
||||
.add_field_with_arg("modified_time", self.table.modified_time)
|
||||
.add_field_with_arg("create_time", self.table.create_time)
|
||||
.add_field_with_arg("user_id", self.table.user_id)
|
||||
.build()?;
|
||||
|
||||
Ok((sql, args, app))
|
||||
|
@ -21,9 +21,9 @@ pub(crate) async fn create_trash(
|
||||
) -> Result<(), ServerError> {
|
||||
for (trash_id, ty) in records {
|
||||
let (sql, args) = SqlBuilder::create(TRASH_TABLE)
|
||||
.add_arg("id", trash_id)
|
||||
.add_arg("user_id", &user.user_id)
|
||||
.add_arg("ty", ty)
|
||||
.add_field_with_arg("id", trash_id)
|
||||
.add_field_with_arg("user_id", &user.user_id)
|
||||
.add_field_with_arg("ty", ty)
|
||||
.build()?;
|
||||
|
||||
let _ = sqlx::query_with(&sql, args)
|
||||
@ -52,7 +52,7 @@ pub(crate) async fn delete_all_trash(
|
||||
.collect::<Vec<(Uuid, i32)>>();
|
||||
tracing::Span::current().record("delete_rows", &format!("{:?}", rows).as_str());
|
||||
let affected_row_count = rows.len();
|
||||
let _ = delete_trash_targets(transaction as &mut DBTransaction<'_>, rows).await?;
|
||||
let _ = delete_trash_associate_targets(transaction as &mut DBTransaction<'_>, rows).await?;
|
||||
|
||||
let (sql, args) = SqlBuilder::delete(TRASH_TABLE)
|
||||
.and_where_eq("user_id", &user.user_id)
|
||||
@ -84,7 +84,7 @@ pub(crate) async fn delete_trash(
|
||||
.await
|
||||
.map_err(map_sqlx_error)?;
|
||||
|
||||
let _ = delete_trash_targets(
|
||||
let _ = delete_trash_associate_targets(
|
||||
transaction as &mut DBTransaction<'_>,
|
||||
vec![(trash_table.id, trash_table.ty)],
|
||||
)
|
||||
@ -101,7 +101,7 @@ pub(crate) async fn delete_trash(
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(transaction, targets), err)]
|
||||
async fn delete_trash_targets(
|
||||
async fn delete_trash_associate_targets(
|
||||
transaction: &mut DBTransaction<'_>,
|
||||
targets: Vec<(Uuid, i32)>,
|
||||
) -> Result<(), ServerError> {
|
||||
|
@ -77,14 +77,14 @@ impl NewViewSqlBuilder {
|
||||
let view: View = self.table.clone().into();
|
||||
|
||||
let (sql, args) = SqlBuilder::create(VIEW_TABLE)
|
||||
.add_arg("id", self.table.id)
|
||||
.add_arg("belong_to_id", self.table.belong_to_id)
|
||||
.add_arg("name", self.table.name)
|
||||
.add_arg("description", self.table.description)
|
||||
.add_arg("modified_time", self.table.modified_time)
|
||||
.add_arg("create_time", self.table.create_time)
|
||||
.add_arg("thumbnail", self.table.thumbnail)
|
||||
.add_arg("view_type", self.table.view_type)
|
||||
.add_field_with_arg("id", self.table.id)
|
||||
.add_field_with_arg("belong_to_id", self.table.belong_to_id)
|
||||
.add_field_with_arg("name", self.table.name)
|
||||
.add_field_with_arg("description", self.table.description)
|
||||
.add_field_with_arg("modified_time", self.table.modified_time)
|
||||
.add_field_with_arg("create_time", self.table.create_time)
|
||||
.add_field_with_arg("thumbnail", self.table.thumbnail)
|
||||
.add_field_with_arg("view_type", self.table.view_type)
|
||||
.build()?;
|
||||
|
||||
Ok((sql, args, view))
|
||||
|
@ -56,12 +56,12 @@ impl NewWorkspaceBuilder {
|
||||
let workspace: Workspace = self.table.clone().into();
|
||||
// TODO: use macro to fetch each field from struct
|
||||
let (sql, args) = SqlBuilder::create(WORKSPACE_TABLE)
|
||||
.add_arg("id", self.table.id)
|
||||
.add_arg("name", self.table.name)
|
||||
.add_arg("description", self.table.description)
|
||||
.add_arg("modified_time", self.table.modified_time)
|
||||
.add_arg("create_time", self.table.create_time)
|
||||
.add_arg("user_id", self.table.user_id)
|
||||
.add_field_with_arg("id", self.table.id)
|
||||
.add_field_with_arg("name", self.table.name)
|
||||
.add_field_with_arg("description", self.table.description)
|
||||
.add_field_with_arg("modified_time", self.table.modified_time)
|
||||
.add_field_with_arg("create_time", self.table.create_time)
|
||||
.add_field_with_arg("user_id", self.table.user_id)
|
||||
.build()?;
|
||||
|
||||
Ok((sql, args, workspace))
|
||||
|
@ -79,7 +79,7 @@ pub async fn update_doc(pool: &PgPool, mut params: UpdateDocParams) -> Result<()
|
||||
|
||||
let (sql, args) = SqlBuilder::update(DOC_TABLE)
|
||||
.add_some_arg("data", data)
|
||||
.add_arg("rev_id", params.rev_id)
|
||||
.add_field_with_arg("rev_id", params.rev_id)
|
||||
.and_where_eq("id", doc_id)
|
||||
.build()?;
|
||||
|
||||
@ -128,9 +128,9 @@ impl NewDocSqlBuilder {
|
||||
|
||||
pub fn build(self) -> Result<(String, PgArguments), ServerError> {
|
||||
let (sql, args) = SqlBuilder::create(DOC_TABLE)
|
||||
.add_arg("id", self.table.id)
|
||||
.add_arg("data", self.table.data)
|
||||
.add_arg("rev_id", self.table.rev_id)
|
||||
.add_field_with_arg("id", self.table.id)
|
||||
.add_field_with_arg("data", self.table.data)
|
||||
.add_field_with_arg("rev_id", self.table.rev_id)
|
||||
.build()?;
|
||||
|
||||
Ok((sql, args))
|
||||
|
165
backend/src/services/kv_store/kv.rs
Normal file
165
backend/src/services/kv_store/kv.rs
Normal file
@ -0,0 +1,165 @@
|
||||
use crate::{
|
||||
services::kv_store::{KVStore, KeyValue},
|
||||
util::sqlx_ext::{map_sqlx_error, SqlBuilder},
|
||||
};
|
||||
use actix_web::web::Data;
|
||||
use anyhow::Context;
|
||||
use backend_service::errors::ServerError;
|
||||
use bytes::Bytes;
|
||||
use lib_infra::future::FutureResultSend;
|
||||
use sql_builder::{quote, SqlBuilder as RawSqlBuilder};
|
||||
use sqlx::{postgres::PgArguments, Error, PgPool, Postgres, Row};
|
||||
|
||||
const KV_TABLE: &str = "kv_table";
|
||||
|
||||
pub(crate) struct PostgresKV {
|
||||
pub(crate) pg_pool: Data<PgPool>,
|
||||
}
|
||||
|
||||
impl KVStore for PostgresKV {
|
||||
fn get(&self, key: &str) -> FutureResultSend<Option<Bytes>, ServerError> {
|
||||
let pg_pool = self.pg_pool.clone();
|
||||
let id = key.to_string();
|
||||
FutureResultSend::new(async move {
|
||||
let mut transaction = pg_pool
|
||||
.begin()
|
||||
.await
|
||||
.context("[KV]:Failed to acquire a Postgres connection")?;
|
||||
|
||||
let (sql, args) = SqlBuilder::select(KV_TABLE)
|
||||
.add_field("*")
|
||||
.and_where_eq("id", &id)
|
||||
.build()?;
|
||||
|
||||
let result = sqlx::query_as_with::<Postgres, KVTable, PgArguments>(&sql, args)
|
||||
.fetch_one(&mut transaction)
|
||||
.await;
|
||||
|
||||
let result = match result {
|
||||
Ok(val) => Ok(Some(Bytes::from(val.blob))),
|
||||
Err(error) => match error {
|
||||
Error::RowNotFound => Ok(None),
|
||||
_ => Err(map_sqlx_error(error)),
|
||||
},
|
||||
};
|
||||
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.context("[KV]:Failed to commit SQL transaction.")?;
|
||||
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
fn set(&self, key: &str, bytes: Bytes) -> FutureResultSend<(), ServerError> {
|
||||
self.batch_set(vec![KeyValue {
|
||||
key: key.to_string(),
|
||||
value: bytes,
|
||||
}])
|
||||
}
|
||||
|
||||
fn delete(&self, key: &str) -> FutureResultSend<(), ServerError> {
|
||||
let pg_pool = self.pg_pool.clone();
|
||||
let id = key.to_string();
|
||||
|
||||
FutureResultSend::new(async move {
|
||||
let mut transaction = pg_pool
|
||||
.begin()
|
||||
.await
|
||||
.context("[KV]:Failed to acquire a Postgres connection")?;
|
||||
|
||||
let (sql, args) = SqlBuilder::delete(KV_TABLE).and_where_eq("id", &id).build()?;
|
||||
let _ = sqlx::query_with(&sql, args)
|
||||
.execute(&mut transaction)
|
||||
.await
|
||||
.map_err(map_sqlx_error)?;
|
||||
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.context("[KV]:Failed to commit SQL transaction.")?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn batch_set(&self, kvs: Vec<KeyValue>) -> FutureResultSend<(), ServerError> {
|
||||
let pg_pool = self.pg_pool.clone();
|
||||
FutureResultSend::new(async move {
|
||||
let mut transaction = pg_pool
|
||||
.begin()
|
||||
.await
|
||||
.context("[KV]:Failed to acquire a Postgres connection")?;
|
||||
|
||||
let mut builder = RawSqlBuilder::insert_into(KV_TABLE);
|
||||
let mut m_builder = builder.field("id").field("blob");
|
||||
for kv in kvs {
|
||||
let s = match std::str::from_utf8(&kv.value) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log::error!("[KV]: {}", e);
|
||||
""
|
||||
},
|
||||
};
|
||||
m_builder = m_builder.values(&[quote(kv.key), quote(s)]);
|
||||
}
|
||||
let sql = m_builder.sql()?;
|
||||
let _ = sqlx::query(&sql)
|
||||
.execute(&mut transaction)
|
||||
.await
|
||||
.map_err(map_sqlx_error)?;
|
||||
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.context("[KV]:Failed to commit SQL transaction.")?;
|
||||
|
||||
Ok::<(), ServerError>(())
|
||||
})
|
||||
}
|
||||
|
||||
fn batch_get(&self, keys: Vec<String>) -> FutureResultSend<Vec<KeyValue>, ServerError> {
|
||||
let pg_pool = self.pg_pool.clone();
|
||||
FutureResultSend::new(async move {
|
||||
let mut transaction = pg_pool
|
||||
.begin()
|
||||
.await
|
||||
.context("[KV]:Failed to acquire a Postgres connection")?;
|
||||
|
||||
let sql = RawSqlBuilder::select_from(KV_TABLE)
|
||||
.field("id")
|
||||
.field("blob")
|
||||
.and_where_in_quoted("id", &keys)
|
||||
.sql()?;
|
||||
|
||||
let rows = sqlx::query(&sql)
|
||||
.fetch_all(&mut transaction)
|
||||
.await
|
||||
.map_err(map_sqlx_error)?;
|
||||
let kvs = rows
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let bytes: Vec<u8> = row.get("blob");
|
||||
KeyValue {
|
||||
key: row.get("id"),
|
||||
value: Bytes::from(bytes),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<KeyValue>>();
|
||||
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.context("[KV]:Failed to commit SQL transaction.")?;
|
||||
|
||||
Ok::<Vec<KeyValue>, ServerError>(kvs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
struct KVTable {
|
||||
pub(crate) id: String,
|
||||
pub(crate) blob: Vec<u8>,
|
||||
}
|
21
backend/src/services/kv_store/mod.rs
Normal file
21
backend/src/services/kv_store/mod.rs
Normal file
@ -0,0 +1,21 @@
|
||||
mod kv;
|
||||
|
||||
use bytes::Bytes;
|
||||
pub(crate) use kv::*;
|
||||
|
||||
use backend_service::errors::ServerError;
|
||||
use lib_infra::future::FutureResultSend;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct KeyValue {
|
||||
pub key: String,
|
||||
pub value: Bytes,
|
||||
}
|
||||
|
||||
pub trait KVStore: Send + Sync {
|
||||
fn get(&self, key: &str) -> FutureResultSend<Option<Bytes>, ServerError>;
|
||||
fn set(&self, key: &str, value: Bytes) -> FutureResultSend<(), ServerError>;
|
||||
fn delete(&self, key: &str) -> FutureResultSend<(), ServerError>;
|
||||
fn batch_set(&self, kvs: Vec<KeyValue>) -> FutureResultSend<(), ServerError>;
|
||||
fn batch_get(&self, keys: Vec<String>) -> FutureResultSend<Vec<KeyValue>, ServerError>;
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
pub mod core;
|
||||
pub mod document;
|
||||
pub mod kv_store;
|
||||
pub mod user;
|
||||
pub mod web_socket;
|
||||
|
@ -52,7 +52,7 @@ impl SqlBuilder {
|
||||
builder
|
||||
}
|
||||
|
||||
pub fn add_arg<'a, T>(mut self, field: &str, arg: T) -> Self
|
||||
pub fn add_field_with_arg<'a, T>(mut self, field: &str, arg: T) -> Self
|
||||
where
|
||||
T: 'a + Send + Encode<'a, Postgres> + Type<Postgres>,
|
||||
{
|
||||
@ -67,7 +67,7 @@ impl SqlBuilder {
|
||||
T: 'a + Send + Encode<'a, Postgres> + Type<Postgres>,
|
||||
{
|
||||
if add {
|
||||
self.add_arg(field, arg)
|
||||
self.add_field_with_arg(field, arg)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
@ -78,7 +78,7 @@ impl SqlBuilder {
|
||||
T: 'a + Send + Encode<'a, Postgres> + Type<Postgres>,
|
||||
{
|
||||
if let Some(arg) = arg {
|
||||
self.add_arg(field, arg)
|
||||
self.add_field_with_arg(field, arg)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
mod auth;
|
||||
mod doc;
|
||||
mod workspace;
|
51
backend/tests/api_test/kv_test.rs
Normal file
51
backend/tests/api_test/kv_test.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use crate::util::helper::spawn_server;
|
||||
use backend::services::kv_store::KeyValue;
|
||||
use std::str;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn kv_set_test() {
|
||||
let server = spawn_server().await;
|
||||
let kv = server.app_ctx.kv_store.clone();
|
||||
let s1 = "123".to_string();
|
||||
let key = "1";
|
||||
|
||||
let _ = kv.set(key, s1.clone().into()).await.unwrap();
|
||||
let bytes = kv.get(key).await.unwrap().unwrap();
|
||||
let s2 = str::from_utf8(&bytes).unwrap();
|
||||
assert_eq!(s1, s2);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn kv_delete_test() {
|
||||
let server = spawn_server().await;
|
||||
let kv = server.app_ctx.kv_store.clone();
|
||||
let s1 = "123".to_string();
|
||||
let key = "1";
|
||||
|
||||
let _ = kv.set(key, s1.clone().into()).await.unwrap();
|
||||
let _ = kv.delete(key).await.unwrap();
|
||||
assert_eq!(kv.get(key).await.unwrap(), None);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn kv_batch_set_test() {
|
||||
let server = spawn_server().await;
|
||||
let kv = server.app_ctx.kv_store.clone();
|
||||
let kvs = vec![
|
||||
KeyValue {
|
||||
key: "1".to_string(),
|
||||
value: "a".to_string().into(),
|
||||
},
|
||||
KeyValue {
|
||||
key: "2".to_string(),
|
||||
value: "b".to_string().into(),
|
||||
},
|
||||
];
|
||||
kv.batch_set(kvs.clone()).await.unwrap();
|
||||
let kvs_from_db = kv
|
||||
.batch_get(kvs.clone().into_iter().map(|value| value.key).collect::<Vec<String>>())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(kvs, kvs_from_db);
|
||||
}
|
3
backend/tests/api_test/mod.rs
Normal file
3
backend/tests/api_test/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod auth_test;
|
||||
mod kv_test;
|
||||
mod workspace_test;
|
@ -70,7 +70,7 @@ async fn delta_sync_while_editing_with_attribute() {
|
||||
// │ops: ["123", "456"] rev: 2│ │ │
|
||||
// └──────────────────────────┘ │ │
|
||||
// │ │
|
||||
// ◀── http request ─┤ Open document
|
||||
// ◀── http request ─┤ Open document_test
|
||||
// │ │
|
||||
// │ │ ┌──────────────────────────┐
|
||||
// ├──http response──┼─▶│ops: ["123", "456"] rev: 2│
|
||||
@ -115,7 +115,7 @@ async fn delta_sync_with_server_push_delta() {
|
||||
// └─────────┘ └─────────┘
|
||||
// │ │
|
||||
// │ │
|
||||
// ◀── http request ─┤ Open document
|
||||
// ◀── http request ─┤ Open document_test
|
||||
// │ │
|
||||
// │ │ ┌───────────────┐
|
||||
// ├──http response──┼─▶│ops: [] rev: 0 │
|
||||
@ -165,7 +165,7 @@ async fn delta_sync_while_local_rev_less_than_server_rev() {
|
||||
// ┌───────────────────┐ │ │
|
||||
// │ops: ["123"] rev: 1│ │ │
|
||||
// └───────────────────┘ │ │
|
||||
// ◀── http request ─┤ Open document
|
||||
// ◀── http request ─┤ Open document_test
|
||||
// │ │
|
||||
// │ │ ┌───────────────┐
|
||||
// ├──http response──┼──▶│ops: [123] rev:│
|
@ -1,2 +1,3 @@
|
||||
// mod edit_script;
|
||||
// mod edit_test;
|
||||
mod crud_test;
|
@ -1,3 +1,3 @@
|
||||
mod api;
|
||||
mod document;
|
||||
mod api_test;
|
||||
mod document_test;
|
||||
pub mod util;
|
||||
|
@ -150,7 +150,7 @@ impl TestUserServer {
|
||||
}
|
||||
|
||||
pub async fn read_doc(&self, params: DocIdentifier) -> Option<Doc> {
|
||||
let url = format!("{}/api/document", self.http_addr());
|
||||
let url = format!("{}/api/document_test", self.http_addr());
|
||||
let doc = read_doc_request(self.user_token(), params, &url).await.unwrap();
|
||||
doc
|
||||
}
|
||||
|
@ -45,8 +45,8 @@ fn parse_files_protobuf(proto_crate_path: &str, proto_output_dir: &str) -> Vec<P
|
||||
}
|
||||
|
||||
// https://docs.rs/syn/1.0.54/syn/struct.File.html
|
||||
let ast =
|
||||
syn::parse_file(read_file(&path).unwrap().as_ref()).expect(&format!("Unable to parse file at {}", path));
|
||||
let ast = syn::parse_file(read_file(&path).unwrap().as_ref())
|
||||
.unwrap_or_else(|_| panic!("Unable to parse file at {}", path));
|
||||
let structs = get_ast_structs(&ast);
|
||||
let proto_file_path = format!("{}/{}.proto", &proto_output_dir, &file_name);
|
||||
let mut proto_file_content = parse_or_init_proto_file(proto_file_path.as_ref());
|
||||
|
Loading…
Reference in New Issue
Block a user