mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:08:32 +02:00
gRPC schema from files!
This commit is contained in:
92
src-tauri/Cargo.lock
generated
92
src-tauri/Cargo.lock
generated
@@ -1161,12 +1161,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.6"
|
version = "0.3.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
|
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1696,12 +1696,14 @@ dependencies = [
|
|||||||
"prost",
|
"prost",
|
||||||
"prost-reflect",
|
"prost-reflect",
|
||||||
"prost-types",
|
"prost-types",
|
||||||
|
"protoc-bin-vendored",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tonic",
|
"tonic",
|
||||||
"tonic-reflection",
|
"tonic-reflection",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2379,9 +2381,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.150"
|
version = "0.2.153"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
@@ -2422,9 +2424,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.11"
|
version = "0.4.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
|
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
@@ -3430,6 +3432,56 @@ dependencies = [
|
|||||||
"prost",
|
"prost",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "005ca8623e5633e298ad1f917d8be0a44bcf406bf3cde3b80e63003e49a3f27d"
|
||||||
|
dependencies = [
|
||||||
|
"protoc-bin-vendored-linux-aarch_64",
|
||||||
|
"protoc-bin-vendored-linux-ppcle_64",
|
||||||
|
"protoc-bin-vendored-linux-x86_32",
|
||||||
|
"protoc-bin-vendored-linux-x86_64",
|
||||||
|
"protoc-bin-vendored-macos-x86_64",
|
||||||
|
"protoc-bin-vendored-win32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-linux-aarch_64"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fb9fc9cce84c8694b6ea01cc6296617b288b703719b725b8c9c65f7c5874435"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-linux-ppcle_64"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02d2a07dcf7173a04d49974930ccbfb7fd4d74df30ecfc8762cf2f895a094516"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-linux-x86_32"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d54fef0b04fcacba64d1d80eed74a20356d96847da8497a59b0a0a436c9165b0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-linux-x86_64"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8782f2ce7d43a9a5c74ea4936f001e9e8442205c244f7a3d4286bd4c37bc924"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-macos-x86_64"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5de656c7ee83f08e0ae5b81792ccfdc1d04e7876b1d9a38e6876a9e09e02537"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-win32"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9653c3ed92974e34c5a6e0a510864dab979760481714c172e0a34e437cb98804"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psl-types"
|
name = "psl-types"
|
||||||
version = "2.0.11"
|
version = "2.0.11"
|
||||||
@@ -3777,15 +3829,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.21"
|
version = "0.38.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
|
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.1",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4926,15 +4978,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.8.1"
|
version = "3.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
|
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"redox_syscall 0.4.1",
|
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5457,9 +5508,9 @@ checksum = "64a8922555b9500e3d865caed19330172cd67cbf82203f1a3311d8c305cc9f33"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.5.0"
|
version = "1.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
|
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.11",
|
"getrandom 0.2.11",
|
||||||
]
|
]
|
||||||
@@ -5890,6 +5941,15 @@ dependencies = [
|
|||||||
"windows-targets 0.48.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
|
|||||||
@@ -16,5 +16,7 @@ prost-reflect = { version = "0.12.0", features = ["serde", "derive"] }
|
|||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
once_cell = { version = "1.19.0", features = [] }
|
once_cell = { version = "1.19.0", features = [] }
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.79"
|
||||||
hyper = { version = "0.14"}
|
hyper = { version = "0.14" }
|
||||||
hyper-rustls = { version = "0.24.0", features = ["http2"] }
|
hyper-rustls = { version = "0.24.0", features = ["http2"] }
|
||||||
|
protoc-bin-vendored = "3.0.0"
|
||||||
|
uuid = { version = "1.7.0", features = ["v4"] }
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use hyper::client::HttpConnector;
|
use hyper::client::HttpConnector;
|
||||||
use hyper::Client;
|
use hyper::Client;
|
||||||
@@ -13,11 +14,9 @@ use tonic::transport::Uri;
|
|||||||
use tonic::{IntoRequest, IntoStreamingRequest, Streaming};
|
use tonic::{IntoRequest, IntoStreamingRequest, Streaming};
|
||||||
|
|
||||||
use crate::codec::DynamicCodec;
|
use crate::codec::DynamicCodec;
|
||||||
use crate::proto::{fill_pool, method_desc_to_path};
|
use crate::proto::{fill_pool, fill_pool_from_files, get_transport, method_desc_to_path};
|
||||||
use crate::{json_schema, MethodDefinition, ServiceDefinition};
|
use crate::{json_schema, MethodDefinition, ServiceDefinition};
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, String>;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GrpcConnection {
|
pub struct GrpcConnection {
|
||||||
pool: DescriptorPool,
|
pool: DescriptorPool,
|
||||||
@@ -26,7 +25,12 @@ pub struct GrpcConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GrpcConnection {
|
impl GrpcConnection {
|
||||||
pub async fn unary(&self, service: &str, method: &str, message: &str) -> Result<String> {
|
pub async fn unary(
|
||||||
|
&self,
|
||||||
|
service: &str,
|
||||||
|
method: &str,
|
||||||
|
message: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
let service = self.pool.get_service_by_name(service).unwrap();
|
let service = self.pool.get_service_by_name(service).unwrap();
|
||||||
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
||||||
let input_message = method.input();
|
let input_message = method.input();
|
||||||
@@ -55,7 +59,7 @@ impl GrpcConnection {
|
|||||||
service: &str,
|
service: &str,
|
||||||
method: &str,
|
method: &str,
|
||||||
stream: ReceiverStream<String>,
|
stream: ReceiverStream<String>,
|
||||||
) -> Result<Streaming<DynamicMessage>> {
|
) -> Result<Streaming<DynamicMessage>, String> {
|
||||||
let service = self.pool.get_service_by_name(service).unwrap();
|
let service = self.pool.get_service_by_name(service).unwrap();
|
||||||
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
||||||
|
|
||||||
@@ -87,7 +91,7 @@ impl GrpcConnection {
|
|||||||
service: &str,
|
service: &str,
|
||||||
method: &str,
|
method: &str,
|
||||||
stream: ReceiverStream<String>,
|
stream: ReceiverStream<String>,
|
||||||
) -> Result<DynamicMessage> {
|
) -> Result<DynamicMessage, String> {
|
||||||
let service = self.pool.get_service_by_name(service).unwrap();
|
let service = self.pool.get_service_by_name(service).unwrap();
|
||||||
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
||||||
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
|
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
|
||||||
@@ -121,7 +125,7 @@ impl GrpcConnection {
|
|||||||
service: &str,
|
service: &str,
|
||||||
method: &str,
|
method: &str,
|
||||||
message: &str,
|
message: &str,
|
||||||
) -> Result<Streaming<DynamicMessage>> {
|
) -> Result<Streaming<DynamicMessage>, String> {
|
||||||
let service = self.pool.get_service_by_name(service).unwrap();
|
let service = self.pool.get_service_by_name(service).unwrap();
|
||||||
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
||||||
let input_message = method.input();
|
let input_message = method.input();
|
||||||
@@ -159,56 +163,56 @@ impl Default for GrpcHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GrpcHandle {
|
impl GrpcHandle {
|
||||||
pub async fn clean_reflect(&mut self, uri: &Uri) -> Result<Vec<ServiceDefinition>> {
|
pub async fn services_from_files(
|
||||||
self.pools.remove(&uri.to_string());
|
&self,
|
||||||
self.reflect(uri).await
|
paths: Vec<PathBuf>,
|
||||||
|
) -> Result<Vec<ServiceDefinition>, String> {
|
||||||
|
let pool = fill_pool_from_files(paths).await?;
|
||||||
|
Ok(self.services_from_pool(&pool))
|
||||||
|
}
|
||||||
|
pub async fn services_from_reflection(
|
||||||
|
&mut self,
|
||||||
|
uri: &Uri,
|
||||||
|
) -> Result<Vec<ServiceDefinition>, String> {
|
||||||
|
let pool = fill_pool(uri).await?;
|
||||||
|
Ok(self.services_from_pool(&pool))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reflect(&mut self, uri: &Uri) -> Result<Vec<ServiceDefinition>> {
|
fn services_from_pool(&self, pool: &DescriptorPool) -> Vec<ServiceDefinition> {
|
||||||
let pool = match self.pools.get(&uri.to_string()) {
|
pool.services()
|
||||||
Some(p) => p.clone(),
|
.map(|s| {
|
||||||
None => {
|
let mut def = ServiceDefinition {
|
||||||
let (pool, _) = fill_pool(uri).await?;
|
name: s.full_name().to_string(),
|
||||||
self.pools.insert(uri.to_string(), pool.clone());
|
methods: vec![],
|
||||||
pool
|
};
|
||||||
}
|
for method in s.methods() {
|
||||||
};
|
let input_message = method.input();
|
||||||
|
def.methods.push(MethodDefinition {
|
||||||
let result =
|
name: method.name().to_string(),
|
||||||
pool.services()
|
server_streaming: method.is_server_streaming(),
|
||||||
.map(|s| {
|
client_streaming: method.is_client_streaming(),
|
||||||
let mut def = ServiceDefinition {
|
schema: serde_json::to_string_pretty(&json_schema::message_to_json_schema(
|
||||||
name: s.full_name().to_string(),
|
&pool,
|
||||||
methods: vec![],
|
input_message,
|
||||||
};
|
))
|
||||||
for method in s.methods() {
|
.unwrap(),
|
||||||
let input_message = method.input();
|
})
|
||||||
def.methods.push(MethodDefinition {
|
}
|
||||||
name: method.name().to_string(),
|
def
|
||||||
server_streaming: method.is_server_streaming(),
|
})
|
||||||
client_streaming: method.is_client_streaming(),
|
.collect::<Vec<_>>()
|
||||||
schema: serde_json::to_string_pretty(
|
|
||||||
&json_schema::message_to_json_schema(&pool, input_message),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
def
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn server_streaming(
|
pub async fn server_streaming(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: &str,
|
id: &str,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
|
proto_files: Vec<PathBuf>,
|
||||||
service: &str,
|
service: &str,
|
||||||
method: &str,
|
method: &str,
|
||||||
message: &str,
|
message: &str,
|
||||||
) -> Result<Streaming<DynamicMessage>> {
|
) -> Result<Streaming<DynamicMessage>, String> {
|
||||||
self.connect(id, uri)
|
self.connect(id, uri, proto_files)
|
||||||
.await?
|
.await?
|
||||||
.server_streaming(service, method, message)
|
.server_streaming(service, method, message)
|
||||||
.await
|
.await
|
||||||
@@ -218,11 +222,12 @@ impl GrpcHandle {
|
|||||||
&mut self,
|
&mut self,
|
||||||
id: &str,
|
id: &str,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
|
proto_files: Vec<PathBuf>,
|
||||||
service: &str,
|
service: &str,
|
||||||
method: &str,
|
method: &str,
|
||||||
stream: ReceiverStream<String>,
|
stream: ReceiverStream<String>,
|
||||||
) -> Result<DynamicMessage> {
|
) -> Result<DynamicMessage, String> {
|
||||||
self.connect(id, uri)
|
self.connect(id, uri, proto_files)
|
||||||
.await?
|
.await?
|
||||||
.client_streaming(service, method, stream)
|
.client_streaming(service, method, stream)
|
||||||
.await
|
.await
|
||||||
@@ -232,18 +237,36 @@ impl GrpcHandle {
|
|||||||
&mut self,
|
&mut self,
|
||||||
id: &str,
|
id: &str,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
|
proto_files: Vec<PathBuf>,
|
||||||
service: &str,
|
service: &str,
|
||||||
method: &str,
|
method: &str,
|
||||||
stream: ReceiverStream<String>,
|
stream: ReceiverStream<String>,
|
||||||
) -> Result<Streaming<DynamicMessage>> {
|
) -> Result<Streaming<DynamicMessage>, String> {
|
||||||
self.connect(id, uri)
|
self.connect(id, uri, proto_files)
|
||||||
.await?
|
.await?
|
||||||
.streaming(service, method, stream)
|
.streaming(service, method, stream)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn connect(&mut self, id: &str, uri: Uri) -> Result<GrpcConnection> {
|
pub async fn connect(
|
||||||
let (pool, conn) = fill_pool(&uri).await?;
|
&mut self,
|
||||||
|
id: &str,
|
||||||
|
uri: Uri,
|
||||||
|
proto_files: Vec<PathBuf>,
|
||||||
|
) -> Result<GrpcConnection, String> {
|
||||||
|
let pool = match self.pools.get(id) {
|
||||||
|
Some(p) => p.clone(),
|
||||||
|
None => match proto_files.len() {
|
||||||
|
0 => fill_pool(&uri).await?,
|
||||||
|
_ => {
|
||||||
|
let pool = fill_pool_from_files(proto_files).await?;
|
||||||
|
self.pools.insert(id.to_string(), pool.clone());
|
||||||
|
pool
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let conn = get_transport();
|
||||||
let connection = GrpcConnection { pool, conn, uri };
|
let connection = GrpcConnection { pool, conn, uri };
|
||||||
self.connections.insert(id.to_string(), connection.clone());
|
self.connections.insert(id.to_string(), connection.clone());
|
||||||
Ok(connection)
|
Ok(connection)
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
|
use std::env::temp_dir;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use hyper::client::HttpConnector;
|
use hyper::client::HttpConnector;
|
||||||
use hyper::Client;
|
use hyper::Client;
|
||||||
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
|
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
|
||||||
use log::warn;
|
use log::{debug, warn};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use prost_reflect::{DescriptorPool, MethodDescriptor};
|
use prost_reflect::{DescriptorPool, MethodDescriptor};
|
||||||
use prost_types::FileDescriptorProto;
|
use prost_types::{FileDescriptorProto, FileDescriptorSet};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
use tonic::body::BoxBody;
|
use tonic::body::BoxBody;
|
||||||
@@ -23,36 +25,71 @@ use tonic_reflection::pb::ServerReflectionRequest;
|
|||||||
|
|
||||||
pub async fn fill_pool_from_files(paths: Vec<PathBuf>) -> Result<DescriptorPool, String> {
|
pub async fn fill_pool_from_files(paths: Vec<PathBuf>) -> Result<DescriptorPool, String> {
|
||||||
let mut pool = DescriptorPool::new();
|
let mut pool = DescriptorPool::new();
|
||||||
|
let random_file_name = format!("{}.desc", uuid::Uuid::new_v4());
|
||||||
|
let desc_path = temp_dir().join(random_file_name);
|
||||||
|
let bin = protoc_bin_vendored::protoc_bin_path().unwrap();
|
||||||
|
|
||||||
|
let mut cmd = Command::new(bin.clone());
|
||||||
|
cmd.arg("--include_imports")
|
||||||
|
.arg("--include_source_info")
|
||||||
|
.arg("-o")
|
||||||
|
.arg(&desc_path);
|
||||||
|
|
||||||
for p in paths {
|
for p in paths {
|
||||||
let bytes = fs::read(p).await.unwrap();
|
if p.as_path().exists() {
|
||||||
let fdp = FileDescriptorProto::decode(bytes.deref()).unwrap();
|
cmd.arg(p.as_path().to_string_lossy().as_ref());
|
||||||
pool.add_file_descriptor_proto(fdp)
|
} else {
|
||||||
.map_err(|e| e.to_string())?;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = p.as_path().parent();
|
||||||
|
if let Some(parent_path) = parent {
|
||||||
|
cmd.arg("-I").arg(parent_path);
|
||||||
|
} else {
|
||||||
|
debug!("ignoring {:?} since it does not exist.", parent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("Running: {:?}", cmd);
|
||||||
|
|
||||||
|
let output = cmd.output().map_err(|e| e.to_string())?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(format!(
|
||||||
|
"protoc failed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = fs::read(desc_path.as_path())
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let fdp = FileDescriptorSet::decode(bytes.deref()).map_err(|e| e.to_string())?;
|
||||||
|
pool.add_file_descriptor_set(fdp)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
fs::remove_file(desc_path.as_path())
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
Ok(pool)
|
Ok(pool)
|
||||||
}
|
}
|
||||||
pub async fn fill_pool(
|
|
||||||
uri: &Uri,
|
pub fn get_transport() -> Client<HttpsConnector<HttpConnector>, BoxBody> {
|
||||||
) -> Result<
|
|
||||||
(
|
|
||||||
DescriptorPool,
|
|
||||||
Client<HttpsConnector<HttpConnector>, BoxBody>,
|
|
||||||
),
|
|
||||||
String,
|
|
||||||
> {
|
|
||||||
let mut pool = DescriptorPool::new();
|
|
||||||
let connector = HttpsConnectorBuilder::new().with_native_roots();
|
let connector = HttpsConnectorBuilder::new().with_native_roots();
|
||||||
let connector = connector.https_or_http().enable_http2().wrap_connector({
|
let connector = connector.https_or_http().enable_http2().wrap_connector({
|
||||||
let mut http_connector = HttpConnector::new();
|
let mut http_connector = HttpConnector::new();
|
||||||
http_connector.enforce_http(false);
|
http_connector.enforce_http(false);
|
||||||
http_connector
|
http_connector
|
||||||
});
|
});
|
||||||
let transport = Client::builder()
|
Client::builder()
|
||||||
.pool_max_idle_per_host(0)
|
.pool_max_idle_per_host(0)
|
||||||
.http2_only(true)
|
.http2_only(true)
|
||||||
.build(connector);
|
.build(connector)
|
||||||
|
}
|
||||||
|
|
||||||
let mut client = ServerReflectionClient::with_origin(transport.clone(), uri.clone());
|
pub async fn fill_pool(uri: &Uri) -> Result<DescriptorPool, String> {
|
||||||
|
let mut pool = DescriptorPool::new();
|
||||||
|
let mut client = ServerReflectionClient::with_origin(get_transport(), uri.clone());
|
||||||
|
|
||||||
for service in list_services(&mut client).await? {
|
for service in list_services(&mut client).await? {
|
||||||
if service == "grpc.reflection.v1alpha.ServerReflection" {
|
if service == "grpc.reflection.v1alpha.ServerReflection" {
|
||||||
@@ -61,7 +98,7 @@ pub async fn fill_pool(
|
|||||||
file_descriptor_set_from_service_name(&service, &mut pool, &mut client).await;
|
file_descriptor_set_from_service_name(&service, &mut pool, &mut client).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((pool, transport))
|
Ok(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_services(
|
async fn list_services(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ extern crate objc;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env::current_dir;
|
use std::env::current_dir;
|
||||||
use std::fs::{create_dir_all, read_to_string, File};
|
use std::fs::{create_dir_all, read_to_string, File};
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@@ -100,8 +101,28 @@ async fn cmd_grpc_reflect(
|
|||||||
let req = get_grpc_request(&app_handle, request_id)
|
let req = get_grpc_request(&app_handle, request_id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let uri = safe_uri(&req.url).map_err(|e| e.to_string())?;
|
if req.proto_files.0.len() > 0 {
|
||||||
grpc_handle.lock().await.clean_reflect(&uri).await
|
println!("REFLECT FROM FILES");
|
||||||
|
grpc_handle
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.services_from_files(
|
||||||
|
req.proto_files
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|p| PathBuf::from_str(p).unwrap())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
println!("REFLECT FROM URI");
|
||||||
|
let uri = safe_uri(&req.url).map_err(|e| e.to_string())?;
|
||||||
|
grpc_handle
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.services_from_reflection(&uri)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -150,7 +171,15 @@ async fn cmd_grpc_call_unary(
|
|||||||
let msg = match grpc_handle
|
let msg = match grpc_handle
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.connect(&conn.clone().id, uri)
|
.connect(
|
||||||
|
&req.clone().id,
|
||||||
|
uri,
|
||||||
|
req.proto_files
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|p| PathBuf::from_str(p).unwrap())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
.await?
|
.await?
|
||||||
.unary(
|
.unary(
|
||||||
&req.service.unwrap_or_default(),
|
&req.service.unwrap_or_default(),
|
||||||
@@ -321,7 +350,18 @@ async fn cmd_grpc_client_streaming(
|
|||||||
let msg = grpc_handle
|
let msg = grpc_handle
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.client_streaming(&conn.id, uri, &service, &method, in_msg_stream)
|
.connect(
|
||||||
|
&req.clone().id,
|
||||||
|
uri,
|
||||||
|
req.proto_files
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|p| PathBuf::from_str(p).unwrap())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.client_streaming(&service, &method, in_msg_stream)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let message = serde_json::to_string(&msg).unwrap();
|
let message = serde_json::to_string(&msg).unwrap();
|
||||||
@@ -461,7 +501,17 @@ async fn cmd_grpc_streaming(
|
|||||||
let mut stream = grpc_handle
|
let mut stream = grpc_handle
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.streaming(&conn.id, uri, &service, &method, in_msg_stream)
|
.connect(
|
||||||
|
&req.clone().id,
|
||||||
|
uri,
|
||||||
|
req.proto_files
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|p| PathBuf::from_str(p).unwrap())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.streaming(&service, &method, in_msg_stream)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -664,7 +714,17 @@ async fn cmd_grpc_server_streaming(
|
|||||||
let mut stream = grpc_handle
|
let mut stream = grpc_handle
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.server_streaming(&conn.id, uri, &service, &method, &req.message)
|
.connect(
|
||||||
|
&req.clone().id,
|
||||||
|
uri,
|
||||||
|
req.proto_files
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|p| PathBuf::from_str(p).unwrap())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.server_streaming(&service, &method, &req.message)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -75,11 +75,6 @@ export function GrpcConnectionSetupPane({
|
|||||||
[updateRequest],
|
[updateRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelectProtoFiles = useCallback(
|
|
||||||
(paths: string[]) => updateRequest.mutateAsync({ protoFiles: paths }),
|
|
||||||
[updateRequest],
|
|
||||||
);
|
|
||||||
|
|
||||||
const select = useMemo(() => {
|
const select = useMemo(() => {
|
||||||
const options =
|
const options =
|
||||||
services?.flatMap((s) =>
|
services?.flatMap((s) =>
|
||||||
@@ -159,10 +154,12 @@ export function GrpcConnectionSetupPane({
|
|||||||
shortLabel: o.label,
|
shortLabel: o.label,
|
||||||
}))}
|
}))}
|
||||||
extraItems={[
|
extraItems={[
|
||||||
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Custom',
|
label: 'Refresh',
|
||||||
type: 'default',
|
type: 'default',
|
||||||
key: 'custom',
|
key: 'custom',
|
||||||
|
leftSlot: <Icon className="text-gray-600" size="sm" icon="refresh" />,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -234,7 +231,6 @@ export function GrpcConnectionSetupPane({
|
|||||||
reflectionError={reflectionError}
|
reflectionError={reflectionError}
|
||||||
reflectionLoading={reflectionLoading}
|
reflectionLoading={reflectionLoading}
|
||||||
onReflect={onReflectRefetch}
|
onReflect={onReflectRefetch}
|
||||||
onSelectProtoFiles={handleSelectProtoFiles}
|
|
||||||
request={activeRequest}
|
request={activeRequest}
|
||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { open } from '@tauri-apps/api/dialog';
|
import classNames from 'classnames';
|
||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import { updateSchema } from 'codemirror-json-schema';
|
import { updateSchema } from 'codemirror-json-schema';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
@@ -7,14 +7,12 @@ import type { ReflectResponseService } from '../hooks/useGrpc';
|
|||||||
import { tryFormatJson } from '../lib/formatters';
|
import { tryFormatJson } from '../lib/formatters';
|
||||||
import type { GrpcRequest } from '../lib/models';
|
import type { GrpcRequest } from '../lib/models';
|
||||||
import { count } from '../lib/pluralize';
|
import { count } from '../lib/pluralize';
|
||||||
import { Banner } from './core/Banner';
|
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import type { EditorProps } from './core/Editor';
|
import type { EditorProps } from './core/Editor';
|
||||||
import { Editor } from './core/Editor';
|
import { Editor } from './core/Editor';
|
||||||
import { FormattedError } from './core/FormattedError';
|
import { FormattedError } from './core/FormattedError';
|
||||||
import { InlineCode } from './core/InlineCode';
|
import { InlineCode } from './core/InlineCode';
|
||||||
import { Link } from './core/Link';
|
import { VStack } from './core/Stacks';
|
||||||
import { HStack, VStack } from './core/Stacks';
|
|
||||||
import { useDialog } from './DialogContext';
|
import { useDialog } from './DialogContext';
|
||||||
import { GrpcProtoSelection } from './GrpcProtoSelection';
|
import { GrpcProtoSelection } from './GrpcProtoSelection';
|
||||||
|
|
||||||
@@ -23,16 +21,12 @@ type Props = Pick<EditorProps, 'heightMode' | 'onChange' | 'className'> & {
|
|||||||
reflectionError?: string;
|
reflectionError?: string;
|
||||||
reflectionLoading?: boolean;
|
reflectionLoading?: boolean;
|
||||||
request: GrpcRequest;
|
request: GrpcRequest;
|
||||||
onReflect: () => void;
|
|
||||||
onSelectProtoFiles: (paths: string[]) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function GrpcEditor({
|
export function GrpcEditor({
|
||||||
services,
|
services,
|
||||||
reflectionError,
|
reflectionError,
|
||||||
reflectionLoading,
|
reflectionLoading,
|
||||||
onReflect,
|
|
||||||
onSelectProtoFiles,
|
|
||||||
request,
|
request,
|
||||||
...extraEditorProps
|
...extraEditorProps
|
||||||
}: Props) {
|
}: Props) {
|
||||||
@@ -97,6 +91,7 @@ export function GrpcEditor({
|
|||||||
}, [alert, services, request.method, request.service]);
|
}, [alert, services, request.method, request.service]);
|
||||||
|
|
||||||
const reflectionUnavailable = reflectionError?.match(/unimplemented/i);
|
const reflectionUnavailable = reflectionError?.match(/unimplemented/i);
|
||||||
|
const reflectionSuccess = !reflectionError && services != null && request.protoFiles.length === 0;
|
||||||
reflectionError = reflectionUnavailable ? undefined : reflectionError;
|
reflectionError = reflectionUnavailable ? undefined : reflectionError;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -110,7 +105,7 @@ export function GrpcEditor({
|
|||||||
placeholder="..."
|
placeholder="..."
|
||||||
ref={editorViewRef}
|
ref={editorViewRef}
|
||||||
actions={[
|
actions={[
|
||||||
<div key="reflection" className="!opacity-100">
|
<div key="reflection" className={classNames(!reflectionSuccess && '!opacity-100')}>
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
color={
|
color={
|
||||||
@@ -128,39 +123,13 @@ export function GrpcEditor({
|
|||||||
title: 'Configure Schema',
|
title: 'Configure Schema',
|
||||||
size: 'md',
|
size: 'md',
|
||||||
id: 'reflection-failed',
|
id: 'reflection-failed',
|
||||||
render: ({ hide }) => (
|
render: ({ hide }) => {
|
||||||
<VStack space={6} className="pb-5">
|
return (
|
||||||
{reflectionError && <FormattedError>{reflectionError}</FormattedError>}
|
<VStack space={6} className="pb-5">
|
||||||
{reflectionUnavailable && request.protoFiles.length === 0 && (
|
<GrpcProtoSelection onDone={hide} requestId={request.id} />
|
||||||
<Banner>
|
</VStack>
|
||||||
<VStack space={3}>
|
);
|
||||||
<p>
|
},
|
||||||
<InlineCode>{request.url}</InlineCode> doesn't implement{' '}
|
|
||||||
<Link href="https://github.com/grpc/grpc/blob/9aa3c5835a4ed6afae9455b63ed45c761d695bca/doc/server-reflection.md">
|
|
||||||
Server Reflection
|
|
||||||
</Link>{' '}
|
|
||||||
. Please manually add the <InlineCode>.proto</InlineCode> files to get
|
|
||||||
started.
|
|
||||||
</p>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
color="gray"
|
|
||||||
variant="border"
|
|
||||||
onClick={() => {
|
|
||||||
hide();
|
|
||||||
onReflect();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Retry Reflection
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</VStack>
|
|
||||||
</Banner>
|
|
||||||
)}
|
|
||||||
<GrpcProtoSelection requestId={request.id} />
|
|
||||||
</VStack>
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -170,10 +139,10 @@ export function GrpcEditor({
|
|||||||
? 'Select Proto Files'
|
? 'Select Proto Files'
|
||||||
: reflectionError
|
: reflectionError
|
||||||
? 'Server Error'
|
? 'Server Error'
|
||||||
: services != null
|
|
||||||
? 'Proto Schema'
|
|
||||||
: request.protoFiles.length > 0
|
: request.protoFiles.length > 0
|
||||||
? count('Proto File', request.protoFiles.length)
|
? count('File', request.protoFiles.length)
|
||||||
|
: services != null && request.protoFiles.length === 0
|
||||||
|
? 'Schema Detected'
|
||||||
: 'Select Schema'}
|
: 'Select Schema'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>,
|
</div>,
|
||||||
|
|||||||
@@ -1,62 +1,42 @@
|
|||||||
import { open } from '@tauri-apps/api/dialog';
|
import { open } from '@tauri-apps/api/dialog';
|
||||||
|
import { useGrpc } from '../hooks/useGrpc';
|
||||||
import { useGrpcRequest } from '../hooks/useGrpcRequest';
|
import { useGrpcRequest } from '../hooks/useGrpcRequest';
|
||||||
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||||
|
import { count } from '../lib/pluralize';
|
||||||
|
import { Banner } from './core/Banner';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
|
import { FormattedError } from './core/FormattedError';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
import { HStack } from './core/Stacks';
|
import { InlineCode } from './core/InlineCode';
|
||||||
|
import { Link } from './core/Link';
|
||||||
|
import { HStack, VStack } from './core/Stacks';
|
||||||
|
|
||||||
export function GrpcProtoSelection({ requestId }: { requestId: string }) {
|
interface Props {
|
||||||
|
requestId: string;
|
||||||
|
onDone: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GrpcProtoSelection({ requestId }: Props) {
|
||||||
const request = useGrpcRequest(requestId);
|
const request = useGrpcRequest(requestId);
|
||||||
|
const grpc = useGrpc(request, null);
|
||||||
const updateRequest = useUpdateGrpcRequest(request?.id ?? null);
|
const updateRequest = useUpdateGrpcRequest(request?.id ?? null);
|
||||||
|
const services = grpc.reflect.data;
|
||||||
|
const serverReflection = request?.protoFiles.length === 0 && services != null;
|
||||||
|
let reflectError = grpc.reflect.error ?? null;
|
||||||
|
const reflectionUnimplemented = `${reflectError}`.match(/unimplemented/i);
|
||||||
|
|
||||||
|
if (reflectionUnimplemented) {
|
||||||
|
reflectError = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<VStack className="flex-col-reverse" space={3}>
|
||||||
{request.protoFiles.length > 0 && (
|
{/* Buttons on top so they get focus first */}
|
||||||
<table className="w-full divide-y mb-3">
|
<HStack space={2} justifyContent="start" className="flex-row-reverse">
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="text-gray-600">
|
|
||||||
<span className="font-mono text-sm">*.proto</span> Files
|
|
||||||
</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y">
|
|
||||||
{request.protoFiles.map((f, i) => (
|
|
||||||
<tr key={f + i} className="group">
|
|
||||||
<td className="pl-1 text-sm font-mono">{f.split('/').pop()}</td>
|
|
||||||
<td className="w-0 py-0.5">
|
|
||||||
<IconButton
|
|
||||||
title="Remove file"
|
|
||||||
size="sm"
|
|
||||||
icon="trash"
|
|
||||||
className="ml-auto opacity-30 transition-opacity group-hover:opacity-100"
|
|
||||||
onClick={() => {
|
|
||||||
updateRequest.mutate({
|
|
||||||
protoFiles: request.protoFiles.filter((p) => p !== f),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
)}
|
|
||||||
<HStack space={2} justifyContent="end">
|
|
||||||
<Button
|
|
||||||
color="gray"
|
|
||||||
size="sm"
|
|
||||||
onClick={async () => {
|
|
||||||
updateRequest.mutate({ protoFiles: [] });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clear Files
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -68,12 +48,101 @@ export function GrpcProtoSelection({ requestId }: { requestId: string }) {
|
|||||||
});
|
});
|
||||||
if (files == null || typeof files === 'string') return;
|
if (files == null || typeof files === 'string') return;
|
||||||
const newFiles = files.filter((f) => !request.protoFiles.includes(f));
|
const newFiles = files.filter((f) => !request.protoFiles.includes(f));
|
||||||
updateRequest.mutate({ protoFiles: [...request.protoFiles, ...newFiles] });
|
await updateRequest.mutateAsync({ protoFiles: [...request.protoFiles, ...newFiles] });
|
||||||
|
await grpc.reflect.refetch();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Add Files
|
Add Files
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
isLoading={grpc.reflect.isFetching}
|
||||||
|
disabled={grpc.reflect.isFetching}
|
||||||
|
color="gray"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => grpc.reflect.refetch()}
|
||||||
|
>
|
||||||
|
Refresh Schema
|
||||||
|
</Button>
|
||||||
</HStack>
|
</HStack>
|
||||||
</div>
|
<VStack space={5}>
|
||||||
|
{!serverReflection && services != null && services.length > 0 && (
|
||||||
|
<Banner className="flex flex-col gap-2">
|
||||||
|
<p>
|
||||||
|
Found services
|
||||||
|
{services?.slice(0, 5).map((s, i) => {
|
||||||
|
return (
|
||||||
|
<span key={i}>
|
||||||
|
<InlineCode>{s.name}</InlineCode>
|
||||||
|
{i === services.length - 1 ? '' : i === services.length - 2 ? ' and ' : ', '}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{services?.length > 5 && count('other', services?.length - 5)}
|
||||||
|
</p>
|
||||||
|
</Banner>
|
||||||
|
)}
|
||||||
|
{serverReflection && services != null && services.length > 0 && (
|
||||||
|
<Banner className="flex flex-col gap-2">
|
||||||
|
<p>
|
||||||
|
Server reflection found services
|
||||||
|
{services?.map((s, i) => {
|
||||||
|
return (
|
||||||
|
<span key={i}>
|
||||||
|
<InlineCode>{s.name}</InlineCode>
|
||||||
|
{i === services.length - 1 ? '' : i === services.length - 2 ? ' and ' : ', '}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
. You can override this schema by manually selecting <InlineCode>*.proto</InlineCode>{' '}
|
||||||
|
files.
|
||||||
|
</p>
|
||||||
|
</Banner>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{request.protoFiles.length > 0 && (
|
||||||
|
<table className="w-full divide-y">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="text-gray-600">
|
||||||
|
<span className="font-mono text-sm">*.proto</span> Files
|
||||||
|
</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y">
|
||||||
|
{request.protoFiles.map((f, i) => (
|
||||||
|
<tr key={f + i} className="group">
|
||||||
|
<td className="pl-1 text-sm font-mono">{f.split('/').pop()}</td>
|
||||||
|
<td className="w-0 py-0.5">
|
||||||
|
<IconButton
|
||||||
|
title="Remove file"
|
||||||
|
size="sm"
|
||||||
|
icon="trash"
|
||||||
|
className="ml-auto opacity-30 transition-opacity group-hover:opacity-100"
|
||||||
|
onClick={async () => {
|
||||||
|
await updateRequest.mutateAsync({
|
||||||
|
protoFiles: request.protoFiles.filter((p) => p !== f),
|
||||||
|
});
|
||||||
|
grpc.reflect.remove();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
{reflectError && <FormattedError>{reflectError}</FormattedError>}
|
||||||
|
{reflectionUnimplemented && request.protoFiles.length === 0 && (
|
||||||
|
<Banner>
|
||||||
|
<InlineCode>{request.url}</InlineCode> doesn't implement{' '}
|
||||||
|
<Link href="https://github.com/grpc/grpc/blob/9aa3c5835a4ed6afae9455b63ed45c761d695bca/doc/server-reflection.md">
|
||||||
|
Server Reflection
|
||||||
|
</Link>{' '}
|
||||||
|
. Please manually add the <InlineCode>.proto</InlineCode> files to get started.
|
||||||
|
</Banner>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</VStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,23 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
),
|
),
|
||||||
render: ({ hide }) => {
|
render: ({ hide }) => {
|
||||||
return (
|
return (
|
||||||
<HStack space={2} justifyContent="end" alignItems="center" className="mt-4 mb-6">
|
<HStack
|
||||||
|
space={2}
|
||||||
|
justifyContent="start"
|
||||||
|
alignItems="center"
|
||||||
|
className="mt-4 mb-6 flex-row-reverse"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="focus"
|
||||||
|
color="gray"
|
||||||
|
onClick={async () => {
|
||||||
|
hide();
|
||||||
|
const environmentId = (await getRecentEnvironments(w.id))[0];
|
||||||
|
routes.navigate('workspace', { workspaceId: w.id, environmentId });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
This Window
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="focus"
|
className="focus"
|
||||||
color="gray"
|
color="gray"
|
||||||
@@ -66,18 +82,6 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
>
|
>
|
||||||
New Window
|
New Window
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
autoFocus
|
|
||||||
className="focus"
|
|
||||||
color="gray"
|
|
||||||
onClick={async () => {
|
|
||||||
hide();
|
|
||||||
const environmentId = (await getRecentEnvironments(w.id))[0];
|
|
||||||
routes.navigate('workspace', { workspaceId: w.id, environmentId });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
This Window
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -64,22 +64,14 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
|||||||
variant === 'solid' && color === 'custom' && 'ring-blue-500/50',
|
variant === 'solid' && color === 'custom' && 'ring-blue-500/50',
|
||||||
variant === 'solid' &&
|
variant === 'solid' &&
|
||||||
color === 'default' &&
|
color === 'default' &&
|
||||||
'text-gray-700 enabled:hocus:bg-gray-700/10 enabled:hocus:text-gray-1000 ring-blue-500/50',
|
'text-gray-700 enabled:hocus:bg-gray-700/10 enabled:hocus:text-gray-800 ring-blue-500/50',
|
||||||
variant === 'solid' &&
|
variant === 'solid' &&
|
||||||
color === 'gray' &&
|
color === 'gray' &&
|
||||||
'text-gray-800 bg-highlight enabled:hocus:bg-gray-500/20 enabled:hocus:text-gray-1000 ring-blue-500/50',
|
'text-gray-800 bg-highlight enabled:hocus:text-gray-1000 ring-gray-400',
|
||||||
variant === 'solid' &&
|
variant === 'solid' && color === 'primary' && 'bg-blue-400 text-white ring-blue-700',
|
||||||
color === 'primary' &&
|
variant === 'solid' && color === 'secondary' && 'bg-violet-400 text-white ring-violet-700',
|
||||||
'bg-blue-400 text-white enabled:hocus:bg-blue-500 ring-blue-500/50',
|
variant === 'solid' && color === 'warning' && 'bg-orange-400 text-white ring-orange-700',
|
||||||
variant === 'solid' &&
|
variant === 'solid' && color === 'danger' && 'bg-red-400 text-white ring-red-700',
|
||||||
color === 'secondary' &&
|
|
||||||
'bg-violet-400 text-white enabled:hocus:bg-violet-500 ring-violet-500/50',
|
|
||||||
variant === 'solid' &&
|
|
||||||
color === 'warning' &&
|
|
||||||
'bg-orange-400 text-white enabled:hocus:bg-orange-500 ring-orange-500/50',
|
|
||||||
variant === 'solid' &&
|
|
||||||
color === 'danger' &&
|
|
||||||
'bg-red-400 text-white enabled:hocus:bg-red-500 ring-red-500/50',
|
|
||||||
// Borders
|
// Borders
|
||||||
variant === 'border' && 'border',
|
variant === 'border' && 'border',
|
||||||
variant === 'border' &&
|
variant === 'border' &&
|
||||||
|
|||||||
@@ -399,7 +399,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
|||||||
{items.map((item, i) => {
|
{items.map((item, i) => {
|
||||||
if (item.type === 'separator') {
|
if (item.type === 'separator') {
|
||||||
return (
|
return (
|
||||||
<Separator key={i} className="ml-2 my-1.5">
|
<Separator key={i} className={classNames('my-1.5', item.label && 'ml-2')}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</Separator>
|
</Separator>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ export function Confirm({ onHide, onResult, variant = 'confirm' }: ConfirmProps)
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack space={2} justifyContent="end" className="mt-2 mb-4">
|
<HStack space={2} justifyContent="end" className="mt-2 mb-4 flex-row-reverse">
|
||||||
|
<Button className="focus" color={colors[variant]} onClick={handleSuccess}>
|
||||||
|
{confirmButtonTexts[variant]}
|
||||||
|
</Button>
|
||||||
<Button className="focus" color="gray" onClick={handleHide}>
|
<Button className="focus" color="gray" onClick={handleHide}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button autoFocus className="focus" color={colors[variant]} onClick={handleSuccess}>
|
|
||||||
{confirmButtonTexts[variant]}
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ export function useGrpc(req: GrpcRequest | null, conn: GrpcConnection | null) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const debouncedUrl = useDebouncedValue<string>(req?.url ?? 'n/a', 1000);
|
const debouncedUrl = useDebouncedValue<string>(req?.url ?? 'n/a', 1000);
|
||||||
const reflect = useQuery<ReflectResponseService[] | null>({
|
const reflect = useQuery<ReflectResponseService[] | null, string>({
|
||||||
enabled: req != null && req.protoFiles.length === 0,
|
enabled: req != null,
|
||||||
queryKey: ['grpc_reflect', debouncedUrl],
|
queryKey: ['grpc_reflect', debouncedUrl],
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
console.log('REFLECTING...');
|
|
||||||
return (await minPromiseMillis(
|
return (await minPromiseMillis(
|
||||||
invoke('cmd_grpc_reflect', { requestId }),
|
invoke('cmd_grpc_reflect', { requestId }),
|
||||||
1000,
|
300,
|
||||||
)) as ReflectResponseService[];
|
)) as ReflectResponseService[];
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user