Websocket Support (#159)

This commit is contained in:
Gregory Schier
2025-01-31 09:00:11 -08:00
committed by GitHub
parent d411713502
commit c8be8082c5
122 changed files with 5090 additions and 616 deletions

View File

@@ -3,3 +3,4 @@ pub mod models;
pub mod queries;
pub mod plugin;
pub mod render;

View File

@@ -125,7 +125,7 @@ impl<'s> TryFrom<&Row<'s>> for Settings {
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
let proxy: Option<String> = r.get("proxy")?;
let editor_keymap: String = r.get("editor_keymap")?;
Ok(Settings {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
created_at: r.get("created_at")?,
@@ -187,7 +187,7 @@ impl<'s> TryFrom<&Row<'s>> for Workspace {
type Error = rusqlite::Error;
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
Ok(Workspace {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
created_at: r.get("created_at")?,
@@ -243,7 +243,7 @@ impl<'s> TryFrom<&Row<'s>> for WorkspaceMeta {
type Error = rusqlite::Error;
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
Ok(WorkspaceMeta {
Ok(Self {
id: r.get("id")?,
workspace_id: r.get("workspace_id")?,
model: r.get("model")?,
@@ -313,7 +313,7 @@ impl<'s> TryFrom<&Row<'s>> for CookieJar {
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
let cookies: String = r.get("cookies")?;
Ok(CookieJar {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
workspace_id: r.get("workspace_id")?,
@@ -361,7 +361,7 @@ impl<'s> TryFrom<&Row<'s>> for Environment {
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
let variables: String = r.get("variables")?;
Ok(Environment {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
workspace_id: r.get("workspace_id")?,
@@ -424,7 +424,7 @@ impl<'s> TryFrom<&Row<'s>> for Folder {
type Error = rusqlite::Error;
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
Ok(Folder {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
sort_priority: r.get("sort_priority")?,
@@ -484,7 +484,7 @@ pub struct HttpRequest {
pub body_type: Option<String>,
pub description: String,
pub headers: Vec<HttpRequestHeader>,
#[serde(default = "default_http_request_method")]
#[serde(default = "default_http_method")]
pub method: String,
pub name: String,
pub sort_priority: f32,
@@ -524,7 +524,7 @@ impl<'s> TryFrom<&Row<'s>> for HttpRequest {
let body: String = r.get("body")?;
let authentication: String = r.get("authentication")?;
let headers: String = r.get("headers")?;
Ok(HttpRequest {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
sort_priority: r.get("sort_priority")?,
@@ -546,6 +546,243 @@ impl<'s> TryFrom<&Row<'s>> for HttpRequest {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "gen_models.ts")]
pub enum WebsocketConnectionState {
Initialized,
Connected,
Closed,
}
impl Default for WebsocketConnectionState {
fn default() -> Self {
Self::Initialized
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_models.ts")]
pub struct WebsocketConnection {
#[ts(type = "\"websocket_connection\"")]
pub model: String,
pub id: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub workspace_id: String,
pub request_id: String,
pub elapsed: i32,
pub error: Option<String>,
pub headers: Vec<HttpResponseHeader>,
pub state: WebsocketConnectionState,
pub status: i32,
pub url: String,
}
#[derive(Iden)]
pub enum WebsocketConnectionIden {
#[iden = "websocket_connections"]
Table,
Id,
Model,
CreatedAt,
UpdatedAt,
WorkspaceId,
RequestId,
Elapsed,
Error,
Headers,
State,
Status,
Url,
}
impl<'s> TryFrom<&Row<'s>> for WebsocketConnection {
type Error = rusqlite::Error;
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
let headers: String = r.get("headers")?;
let state: String = r.get("state")?;
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
workspace_id: r.get("workspace_id")?,
request_id: r.get("request_id")?,
created_at: r.get("created_at")?,
updated_at: r.get("updated_at")?,
url: r.get("url")?,
headers: serde_json::from_str(headers.as_str()).unwrap_or_default(),
elapsed: r.get("elapsed")?,
error: r.get("error")?,
state: serde_json::from_str(format!(r#""{state}""#).as_str()).unwrap(),
status: r.get("status")?,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "gen_models.ts")]
pub enum WebsocketMessageType {
Text,
Binary,
}
impl Default for WebsocketMessageType {
fn default() -> Self {
Self::Text
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_models.ts")]
pub struct WebsocketRequest {
#[ts(type = "\"websocket_request\"")]
pub model: String,
pub id: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub workspace_id: String,
pub folder_id: Option<String>,
#[ts(type = "Record<string, any>")]
pub authentication: BTreeMap<String, Value>,
pub authentication_type: Option<String>,
pub description: String,
pub headers: Vec<HttpRequestHeader>,
pub message: String,
pub name: String,
pub sort_priority: f32,
pub url: String,
pub url_parameters: Vec<HttpUrlParameter>,
}
#[derive(Iden)]
pub enum WebsocketRequestIden {
#[iden = "websocket_requests"]
Table,
Id,
Model,
CreatedAt,
UpdatedAt,
WorkspaceId,
FolderId,
Authentication,
AuthenticationType,
Message,
Description,
Headers,
Name,
SortPriority,
Url,
UrlParameters,
}
impl<'s> TryFrom<&Row<'s>> for WebsocketRequest {
type Error = rusqlite::Error;
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
let url_parameters: String = r.get("url_parameters")?;
let authentication: String = r.get("authentication")?;
let headers: String = r.get("headers")?;
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
sort_priority: r.get("sort_priority")?,
workspace_id: r.get("workspace_id")?,
created_at: r.get("created_at")?,
updated_at: r.get("updated_at")?,
url: r.get("url")?,
url_parameters: serde_json::from_str(url_parameters.as_str()).unwrap_or_default(),
message: r.get("message")?,
description: r.get("description")?,
authentication: serde_json::from_str(authentication.as_str()).unwrap_or_default(),
authentication_type: r.get("authentication_type")?,
headers: serde_json::from_str(headers.as_str()).unwrap_or_default(),
folder_id: r.get("folder_id")?,
name: r.get("name")?,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "gen_models.ts")]
pub enum WebsocketEventType {
Binary,
Close,
Frame,
Ping,
Pong,
Text,
}
impl Default for WebsocketEventType {
fn default() -> Self {
Self::Text
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_models.ts")]
pub struct WebsocketEvent {
#[ts(type = "\"websocket_event\"")]
pub model: String,
pub id: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub workspace_id: String,
pub request_id: String,
pub connection_id: String,
pub is_server: bool,
pub message: Vec<u8>,
pub message_type: WebsocketEventType,
}
#[derive(Iden)]
pub enum WebsocketEventIden {
#[iden = "websocket_events"]
Table,
Model,
Id,
CreatedAt,
UpdatedAt,
WorkspaceId,
RequestId,
ConnectionId,
IsServer,
MessageType,
Message,
}
impl<'s> TryFrom<&Row<'s>> for WebsocketEvent {
type Error = rusqlite::Error;
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
let message_type: String = r.get("message_type")?;
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
workspace_id: r.get("workspace_id")?,
request_id: r.get("request_id")?,
connection_id: r.get("connection_id")?,
created_at: r.get("created_at")?,
updated_at: r.get("updated_at")?,
message: r.get("message")?,
is_server: r.get("is_server")?,
message_type: serde_json::from_str(message_type.as_str()).unwrap_or_default(),
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_models.ts")]
@@ -626,7 +863,7 @@ impl<'s> TryFrom<&Row<'s>> for HttpResponse {
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
let headers: String = r.get("headers")?;
let state: String = r.get("state")?;
Ok(HttpResponse {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
workspace_id: r.get("workspace_id")?,
@@ -725,7 +962,7 @@ impl<'s> TryFrom<&Row<'s>> for GrpcRequest {
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
let authentication: String = r.get("authentication")?;
let metadata: String = r.get("metadata")?;
Ok(GrpcRequest {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
workspace_id: r.get("workspace_id")?,
@@ -810,7 +1047,7 @@ impl<'s> TryFrom<&Row<'s>> for GrpcConnection {
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
let trailers: String = r.get("trailers")?;
let state: String = r.get("state")?;
Ok(GrpcConnection {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
workspace_id: r.get("workspace_id")?,
@@ -892,7 +1129,7 @@ impl<'s> TryFrom<&Row<'s>> for GrpcEvent {
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
let event_type: String = r.get("event_type")?;
let metadata: String = r.get("metadata")?;
Ok(GrpcEvent {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
workspace_id: r.get("workspace_id")?,
@@ -944,7 +1181,7 @@ impl<'s> TryFrom<&Row<'s>> for Plugin {
type Error = rusqlite::Error;
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
Ok(Plugin {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
created_at: r.get("created_at")?,
@@ -1012,7 +1249,7 @@ impl<'s> TryFrom<&Row<'s>> for SyncState {
type Error = rusqlite::Error;
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
Ok(SyncState {
Ok(Self {
id: r.get("id")?,
workspace_id: r.get("workspace_id")?,
model: r.get("model")?,
@@ -1058,7 +1295,7 @@ impl<'s> TryFrom<&Row<'s>> for KeyValue {
type Error = rusqlite::Error;
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
Ok(KeyValue {
Ok(Self {
model: r.get("model")?,
created_at: r.get("created_at")?,
updated_at: r.get("updated_at")?,
@@ -1100,7 +1337,7 @@ impl<'s> TryFrom<&Row<'s>> for PluginKeyValue {
type Error = rusqlite::Error;
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
Ok(PluginKeyValue {
Ok(Self {
model: r.get("model")?,
created_at: r.get("created_at")?,
updated_at: r.get("updated_at")?,
@@ -1115,7 +1352,7 @@ fn default_true() -> bool {
true
}
fn default_http_request_method() -> String {
fn default_http_method() -> String {
"GET".to_string()
}
@@ -1129,9 +1366,12 @@ pub enum ModelType {
TypeHttpRequest,
TypeHttpResponse,
TypePlugin,
TypeSyncState,
TypeWebSocketConnection,
TypeWebSocketEvent,
TypeWebsocketRequest,
TypeWorkspace,
TypeWorkspaceMeta,
TypeSyncState,
}
impl ModelType {
@@ -1149,6 +1389,9 @@ impl ModelType {
ModelType::TypeWorkspace => "wk",
ModelType::TypeWorkspaceMeta => "wm",
ModelType::TypeSyncState => "ss",
ModelType::TypeWebSocketConnection => "wc",
ModelType::TypeWebSocketEvent => "we",
ModelType::TypeWebsocketRequest => "wr",
}
.to_string()
}
@@ -1171,6 +1414,9 @@ pub enum AnyModel {
KeyValue(KeyValue),
Workspace(Workspace),
WorkspaceMeta(WorkspaceMeta),
WebsocketConnection(WebsocketConnection),
WebsocketEvent(WebsocketEvent),
WebsocketRequest(WebsocketRequest),
}
impl<'de> Deserialize<'de> for AnyModel {

View File

@@ -1,6 +1,15 @@
use crate::error::Error::ModelNotFound;
use crate::error::Result;
use crate::models::{AnyModel, CookieJar, CookieJarIden, Environment, EnvironmentIden, Folder, FolderIden, GrpcConnection, GrpcConnectionIden, GrpcConnectionState, GrpcEvent, GrpcEventIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestIden, HttpResponse, HttpResponseHeader, HttpResponseIden, HttpResponseState, KeyValue, KeyValueIden, ModelType, Plugin, PluginIden, PluginKeyValue, PluginKeyValueIden, Settings, SettingsIden, SyncState, SyncStateIden, Workspace, WorkspaceIden, WorkspaceMeta, WorkspaceMetaIden};
use crate::models::{
AnyModel, CookieJar, CookieJarIden, Environment, EnvironmentIden, Folder, FolderIden,
GrpcConnection, GrpcConnectionIden, GrpcConnectionState, GrpcEvent, GrpcEventIden, GrpcRequest,
GrpcRequestIden, HttpRequest, HttpRequestIden, HttpResponse, HttpResponseHeader,
HttpResponseIden, HttpResponseState, KeyValue, KeyValueIden, ModelType, Plugin, PluginIden,
PluginKeyValue, PluginKeyValueIden, Settings, SettingsIden, SyncState, SyncStateIden,
WebsocketConnection, WebsocketConnectionIden, WebsocketEvent, WebsocketEventIden,
WebsocketRequest, WebsocketRequestIden, Workspace, WorkspaceIden, WorkspaceMeta,
WorkspaceMetaIden,
};
use crate::plugin::SqliteConnection;
use chrono::{NaiveDateTime, Utc};
use log::{debug, error, info, warn};
@@ -16,8 +25,7 @@ use std::path::Path;
use tauri::{AppHandle, Emitter, Listener, Manager, Runtime, WebviewWindow};
use ts_rs::TS;
const MAX_GRPC_CONNECTIONS_PER_REQUEST: usize = 20;
const MAX_HTTP_RESPONSES_PER_REQUEST: usize = MAX_GRPC_CONNECTIONS_PER_REQUEST;
const MAX_HISTORY_ITEMS: usize = 20;
pub async fn set_key_value_string<R: Runtime>(
mgr: &WebviewWindow<R>,
@@ -659,8 +667,8 @@ pub async fn upsert_grpc_connection<R: Runtime>(
update_source: &UpdateSource,
) -> Result<GrpcConnection> {
let connections =
list_http_responses_for_request(window, connection.request_id.as_str(), None).await?;
for c in connections.iter().skip(MAX_GRPC_CONNECTIONS_PER_REQUEST - 1) {
list_grpc_connections_for_request(window, connection.request_id.as_str()).await?;
for c in connections.iter().skip(MAX_HISTORY_ITEMS - 1) {
debug!("Deleting old grpc connection {}", c.id);
delete_grpc_connection(window, c.id.as_str(), update_source).await?;
}
@@ -911,6 +919,367 @@ pub async fn list_grpc_events<R: Runtime>(
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn delete_websocket_request<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
update_source: &UpdateSource,
) -> Result<WebsocketRequest> {
let request = match get_websocket_request(window, id).await? {
Some(r) => r,
None => {
return Err(ModelNotFound(id.to_string()));
}
};
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::delete()
.from_table(WebsocketRequestIden::Table)
.cond_where(Expr::col(WebsocketRequestIden::Id).eq(id))
.build_rusqlite(SqliteQueryBuilder);
db.execute(sql.as_str(), &*params.as_params())?;
emit_deleted_model(window, &AnyModel::WebsocketRequest(request.to_owned()), update_source);
Ok(request)
}
pub async fn delete_websocket_connection<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
update_source: &UpdateSource,
) -> Result<WebsocketConnection> {
let m = get_websocket_connection(window, id).await?;
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::delete()
.from_table(WebsocketConnectionIden::Table)
.cond_where(Expr::col(WebsocketConnectionIden::Id).eq(id))
.build_rusqlite(SqliteQueryBuilder);
db.execute(sql.as_str(), &*params.as_params())?;
emit_deleted_model(window, &AnyModel::WebsocketConnection(m.to_owned()), update_source);
Ok(m)
}
pub async fn delete_all_websocket_connections<R: Runtime>(
window: &WebviewWindow<R>,
request_id: &str,
update_source: &UpdateSource,
) -> Result<()> {
for c in list_websocket_connections_for_request(window, request_id).await? {
delete_websocket_connection(window, &c.id, update_source).await?;
}
Ok(())
}
pub async fn delete_all_websocket_connections_for_workspace<R: Runtime>(
window: &WebviewWindow<R>,
workspace_id: &str,
update_source: &UpdateSource,
) -> Result<()> {
for c in list_websocket_connections_for_workspace(window, workspace_id).await? {
delete_websocket_connection(window, &c.id, update_source).await?;
}
Ok(())
}
pub async fn get_websocket_connection<R: Runtime>(
mgr: &impl Manager<R>,
id: &str,
) -> Result<WebsocketConnection> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
.from(WebsocketConnectionIden::Table)
.column(Asterisk)
.cond_where(Expr::col(WebsocketConnectionIden::Id).eq(id))
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn upsert_websocket_event<R: Runtime>(
window: &WebviewWindow<R>,
event: WebsocketEvent,
update_source: &UpdateSource,
) -> Result<WebsocketEvent> {
let id = match event.id.as_str() {
"" => generate_model_id(ModelType::TypeWebSocketEvent),
_ => event.id.to_string(),
};
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::insert()
.into_table(WebsocketEventIden::Table)
.columns([
WebsocketEventIden::Id,
WebsocketEventIden::CreatedAt,
WebsocketEventIden::UpdatedAt,
WebsocketEventIden::WorkspaceId,
WebsocketEventIden::ConnectionId,
WebsocketEventIden::RequestId,
WebsocketEventIden::MessageType,
WebsocketEventIden::IsServer,
WebsocketEventIden::Message,
])
.values_panic([
id.into(),
timestamp_for_upsert(update_source, event.created_at).into(),
timestamp_for_upsert(update_source, event.updated_at).into(),
event.workspace_id.into(),
event.connection_id.into(),
event.request_id.into(),
serde_json::to_string(&event.message_type)?.into(),
event.is_server.into(),
event.message.into(),
])
.on_conflict(
OnConflict::column(WebsocketEventIden::Id)
.update_columns([
WebsocketEventIden::UpdatedAt,
WebsocketEventIden::MessageType,
WebsocketEventIden::IsServer,
WebsocketEventIden::Message,
])
.to_owned(),
)
.returning_all()
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
let m: WebsocketEvent = stmt.query_row(&*params.as_params(), |row| row.try_into())?;
emit_upserted_model(window, &AnyModel::WebsocketEvent(m.to_owned()), update_source);
Ok(m)
}
pub async fn upsert_websocket_request<R: Runtime>(
window: &WebviewWindow<R>,
request: WebsocketRequest,
update_source: &UpdateSource,
) -> Result<WebsocketRequest> {
let id = match request.id.as_str() {
"" => generate_model_id(ModelType::TypeWebsocketRequest),
_ => request.id.to_string(),
};
let trimmed_name = request.name.trim();
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::insert()
.into_table(WebsocketRequestIden::Table)
.columns([
WebsocketRequestIden::Id,
WebsocketRequestIden::CreatedAt,
WebsocketRequestIden::UpdatedAt,
WebsocketRequestIden::WorkspaceId,
WebsocketRequestIden::FolderId,
WebsocketRequestIden::Authentication,
WebsocketRequestIden::AuthenticationType,
WebsocketRequestIden::Description,
WebsocketRequestIden::Headers,
WebsocketRequestIden::Message,
WebsocketRequestIden::Name,
WebsocketRequestIden::SortPriority,
WebsocketRequestIden::Url,
WebsocketRequestIden::UrlParameters,
])
.values_panic([
id.into(),
timestamp_for_upsert(update_source, request.created_at).into(),
timestamp_for_upsert(update_source, request.updated_at).into(),
request.workspace_id.into(),
request.folder_id.as_ref().map(|s| s.as_str()).into(),
serde_json::to_string(&request.authentication)?.into(),
request.authentication_type.as_ref().map(|s| s.as_str()).into(),
request.description.into(),
serde_json::to_string(&request.headers)?.into(),
request.message.into(),
trimmed_name.into(),
request.sort_priority.into(),
request.url.into(),
serde_json::to_string(&request.url_parameters)?.into(),
])
.on_conflict(
OnConflict::column(WebsocketRequestIden::Id)
.update_columns([
WebsocketRequestIden::UpdatedAt,
WebsocketRequestIden::WorkspaceId,
WebsocketRequestIden::FolderId,
WebsocketRequestIden::Authentication,
WebsocketRequestIden::AuthenticationType,
WebsocketRequestIden::Description,
WebsocketRequestIden::Headers,
WebsocketRequestIden::Message,
WebsocketRequestIden::Name,
WebsocketRequestIden::SortPriority,
WebsocketRequestIden::Url,
WebsocketRequestIden::UrlParameters,
])
.to_owned(),
)
.returning_all()
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
let m: WebsocketRequest = stmt.query_row(&*params.as_params(), |row| row.try_into())?;
emit_upserted_model(window, &AnyModel::WebsocketRequest(m.to_owned()), update_source);
Ok(m)
}
pub async fn list_websocket_connections_for_workspace<R: Runtime>(
mgr: &impl Manager<R>,
workspace_id: &str,
) -> Result<Vec<WebsocketConnection>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
.from(WebsocketConnectionIden::Table)
.cond_where(Expr::col(WebsocketConnectionIden::WorkspaceId).eq(workspace_id))
.column(Asterisk)
.order_by(WebsocketConnectionIden::CreatedAt, Order::Desc)
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn list_websocket_connections_for_request<R: Runtime>(
mgr: &impl Manager<R>,
request_id: &str,
) -> Result<Vec<WebsocketConnection>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
.from(WebsocketConnectionIden::Table)
.cond_where(Expr::col(WebsocketConnectionIden::RequestId).eq(request_id))
.column(Asterisk)
.order_by(WebsocketConnectionIden::CreatedAt, Order::Desc)
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn upsert_websocket_connection<R: Runtime>(
window: &WebviewWindow<R>,
connection: &WebsocketConnection,
update_source: &UpdateSource,
) -> Result<WebsocketConnection> {
let connections =
list_websocket_connections_for_request(window, connection.request_id.as_str()).await?;
for c in connections.iter().skip(MAX_HISTORY_ITEMS - 1) {
debug!("Deleting old websocket connection {}", c.id);
delete_websocket_connection(window, c.id.as_str(), update_source).await?;
}
let id = match connection.id.as_str() {
"" => generate_model_id(ModelType::TypeWebSocketConnection),
_ => connection.id.to_string(),
};
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::insert()
.into_table(WebsocketConnectionIden::Table)
.columns([
WebsocketConnectionIden::Id,
WebsocketConnectionIden::CreatedAt,
WebsocketConnectionIden::UpdatedAt,
WebsocketConnectionIden::WorkspaceId,
WebsocketConnectionIden::RequestId,
WebsocketConnectionIden::Elapsed,
WebsocketConnectionIden::Error,
WebsocketConnectionIden::Headers,
WebsocketConnectionIden::State,
WebsocketConnectionIden::Status,
WebsocketConnectionIden::Url,
])
.values_panic([
id.as_str().into(),
timestamp_for_upsert(update_source, connection.created_at).into(),
timestamp_for_upsert(update_source, connection.updated_at).into(),
connection.workspace_id.as_str().into(),
connection.request_id.as_str().into(),
connection.elapsed.into(),
connection.error.as_ref().map(|s| s.as_str()).into(),
serde_json::to_string(&connection.headers)?.into(),
serde_json::to_value(&connection.state)?.as_str().into(),
connection.status.into(),
connection.url.as_str().into(),
])
.on_conflict(
OnConflict::column(WebsocketConnectionIden::Id)
.update_columns([
WebsocketConnectionIden::UpdatedAt,
WebsocketConnectionIden::Elapsed,
WebsocketConnectionIden::Error,
WebsocketConnectionIden::Headers,
WebsocketConnectionIden::State,
WebsocketConnectionIden::Status,
WebsocketConnectionIden::Url,
])
.to_owned(),
)
.returning_all()
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
let m: WebsocketConnection = stmt.query_row(&*params.as_params(), |row| row.try_into())?;
emit_upserted_model(window, &AnyModel::WebsocketConnection(m.to_owned()), update_source);
Ok(m)
}
pub async fn get_websocket_request<R: Runtime>(
mgr: &impl Manager<R>,
id: &str,
) -> Result<Option<WebsocketRequest>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
.from(WebsocketRequestIden::Table)
.column(Asterisk)
.cond_where(Expr::col(WebsocketRequestIden::Id).eq(id))
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into()).optional()?)
}
pub async fn list_websocket_requests<R: Runtime>(
mgr: &impl Manager<R>,
workspace_id: &str,
) -> Result<Vec<WebsocketRequest>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
.from(WebsocketRequestIden::Table)
.cond_where(Expr::col(WebsocketRequestIden::WorkspaceId).eq(workspace_id))
.column(Asterisk)
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn list_websocket_events<R: Runtime>(
mgr: &impl Manager<R>,
connection_id: &str,
) -> Result<Vec<WebsocketEvent>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
.from(WebsocketEventIden::Table)
.cond_where(Expr::col(WebsocketEventIden::ConnectionId).eq(connection_id))
.column(Asterisk)
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn upsert_cookie_jar<R: Runtime>(
window: &WebviewWindow<R>,
cookie_jar: &CookieJar,
@@ -1676,7 +2045,7 @@ pub async fn create_http_response<R: Runtime>(
update_source: &UpdateSource,
) -> Result<HttpResponse> {
let responses = list_http_responses_for_request(window, request_id, None).await?;
for response in responses.iter().skip(MAX_HTTP_RESPONSES_PER_REQUEST - 1) {
for response in responses.iter().skip(MAX_HISTORY_ITEMS - 1) {
debug!("Deleting old response {}", response.id);
delete_http_response(window, response.id.as_str(), update_source).await?;
}
@@ -2170,6 +2539,7 @@ pub struct BatchUpsertResult {
pub folders: Vec<Folder>,
pub http_requests: Vec<HttpRequest>,
pub grpc_requests: Vec<GrpcRequest>,
pub websocket_requests: Vec<WebsocketRequest>,
}
pub async fn batch_upsert<R: Runtime>(
@@ -2179,6 +2549,7 @@ pub async fn batch_upsert<R: Runtime>(
folders: Vec<Folder>,
http_requests: Vec<HttpRequest>,
grpc_requests: Vec<GrpcRequest>,
websocket_requests: Vec<WebsocketRequest>,
update_source: &UpdateSource,
) -> Result<BatchUpsertResult> {
let mut imported_resources = BatchUpsertResult::default();
@@ -2246,6 +2617,14 @@ pub async fn batch_upsert<R: Runtime>(
info!("Imported {} grpc_requests", imported_resources.grpc_requests.len());
}
if websocket_requests.len() > 0 {
for v in websocket_requests {
let x = upsert_websocket_request(&window, v, update_source).await?;
imported_resources.websocket_requests.push(x.clone());
}
info!("Imported {} websocket_requests", imported_resources.websocket_requests.len());
}
Ok(imported_resources)
}
@@ -2264,6 +2643,7 @@ pub async fn get_workspace_export_resources<R: Runtime>(
folders: Vec::new(),
http_requests: Vec::new(),
grpc_requests: Vec::new(),
websocket_requests: Vec::new(),
},
};
@@ -2273,6 +2653,7 @@ pub async fn get_workspace_export_resources<R: Runtime>(
data.resources.folders.append(&mut list_folders(mgr, workspace_id).await?);
data.resources.http_requests.append(&mut list_http_requests(mgr, workspace_id).await?);
data.resources.grpc_requests.append(&mut list_grpc_requests(mgr, workspace_id).await?);
data.resources.websocket_requests.append(&mut list_websocket_requests(mgr, workspace_id).await?);
}
// Nuke environments if we don't want them

View File

@@ -0,0 +1,34 @@
use std::collections::HashMap;
use crate::models::{Environment, EnvironmentVariable};
pub fn make_vars_hashmap(
base_environment: &Environment,
environment: Option<&Environment>,
) -> HashMap<String, String> {
let mut variables = HashMap::new();
variables = add_variable_to_map(variables, &base_environment.variables);
if let Some(e) = environment {
variables = add_variable_to_map(variables, &e.variables);
}
variables
}
fn add_variable_to_map(
m: HashMap<String, String>,
variables: &Vec<EnvironmentVariable>,
) -> HashMap<String, String> {
let mut map = m.clone();
for variable in variables {
if !variable.enabled || variable.value.is_empty() {
continue;
}
let name = variable.name.as_str();
let value = variable.value.as_str();
map.insert(name.into(), value.into());
}
map
}