mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-27 22:04:01 +02:00
Merge origin/main
This commit is contained in:
@@ -435,15 +435,12 @@ fn create(
|
|||||||
let workspace_id = resolve_workspace_id(ctx, workspace_id_arg.as_deref(), "request create")?;
|
let workspace_id = resolve_workspace_id(ctx, workspace_id_arg.as_deref(), "request create")?;
|
||||||
let name = name.unwrap_or_default();
|
let name = name.unwrap_or_default();
|
||||||
let url = url.unwrap_or_default();
|
let url = url.unwrap_or_default();
|
||||||
let method = method.unwrap_or_else(|| "GET".to_string());
|
let mut request = HttpRequest { workspace_id, name, url, ..Default::default() };
|
||||||
|
// Only override the method when one was given; `HttpRequest::default()` is the
|
||||||
let request = HttpRequest {
|
// single place the fallback ("GET") is defined.
|
||||||
workspace_id,
|
if let Some(method) = method {
|
||||||
name,
|
request.method = method.to_uppercase();
|
||||||
method: method.to_uppercase(),
|
}
|
||||||
url,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let created = ctx
|
let created = ctx
|
||||||
.db()
|
.db()
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { createStore } from "jotai";
|
||||||
|
import { expect, test } from "vitest";
|
||||||
|
import type { HttpResponseEvent } from "../bindings/gen_models";
|
||||||
|
import { httpResponseEventsAtom, modelStoreDataAtom } from "./atoms";
|
||||||
|
import { newStoreData } from "./util";
|
||||||
|
|
||||||
|
// The five setting events that every send writes, all within the same millisecond
|
||||||
|
const SETTING_NAMES = [
|
||||||
|
"validate_certificates",
|
||||||
|
"redirects",
|
||||||
|
"timeout",
|
||||||
|
"send_cookies",
|
||||||
|
"store_cookies",
|
||||||
|
];
|
||||||
|
|
||||||
|
function settingEvent(id: string, name: string, createdAt: string): HttpResponseEvent {
|
||||||
|
return {
|
||||||
|
model: "http_response_event",
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
updatedAt: createdAt,
|
||||||
|
workspaceId: "wk_1",
|
||||||
|
responseId: "rs_1",
|
||||||
|
event: { type: "setting", name, value: "true" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test("events with equal createdAt keep store (DB) insertion order", () => {
|
||||||
|
const store = createStore();
|
||||||
|
const data = newStoreData();
|
||||||
|
SETTING_NAMES.forEach((name, i) => {
|
||||||
|
data.http_response_event[`hre_${i}`] = settingEvent(
|
||||||
|
`hre_${i}`,
|
||||||
|
name,
|
||||||
|
"2026-08-17T00:00:00.123",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
store.set(modelStoreDataAtom, data);
|
||||||
|
|
||||||
|
const names = store.get(httpResponseEventsAtom).map((e) => {
|
||||||
|
return e.event.type === "setting" ? e.event.name : e.event.type;
|
||||||
|
});
|
||||||
|
expect(names).toEqual(SETTING_NAMES);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("events with distinct createdAt sort ascending", () => {
|
||||||
|
const store = createStore();
|
||||||
|
const data = newStoreData();
|
||||||
|
for (const [id, createdAt] of [
|
||||||
|
["hre_b", "2026-08-17T00:00:00.456"],
|
||||||
|
["hre_a", "2026-08-17T00:00:00.123"],
|
||||||
|
["hre_c", "2026-08-17T00:00:00.789"],
|
||||||
|
]) {
|
||||||
|
data.http_response_event[id!] = settingEvent(id!, "timeout", createdAt!);
|
||||||
|
}
|
||||||
|
store.set(modelStoreDataAtom, data);
|
||||||
|
|
||||||
|
expect(store.get(httpResponseEventsAtom).map((e) => e.id)).toEqual(["hre_a", "hre_b", "hre_c"]);
|
||||||
|
});
|
||||||
@@ -61,7 +61,9 @@ export function createOrderedModelAtom<M extends AnyModel["model"]>(
|
|||||||
const modelData = data[modelType] ?? {};
|
const modelData = data[modelType] ?? {};
|
||||||
return Object.values(modelData).sort(
|
return Object.values(modelData).sort(
|
||||||
(a: ExtractModel<AnyModel, M>, b: ExtractModel<AnyModel, M>) => {
|
(a: ExtractModel<AnyModel, M>, b: ExtractModel<AnyModel, M>) => {
|
||||||
const n = a[field] > b[field] ? 1 : -1;
|
// NOTE: ties must return 0, or the comparator is inconsistent and V8 reorders
|
||||||
|
// equal-keyed rows. Sort is stable, so 0 preserves store (DB) insertion order.
|
||||||
|
const n = a[field] === b[field] ? 0 : a[field] > b[field] ? 1 : -1;
|
||||||
return order === "desc" ? n * -1 : n;
|
return order === "desc" ? n * -1 : n;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,8 +60,22 @@ pub struct ProxySettingAuth {
|
|||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
impl Default for ClientCertificate {
|
||||||
#[serde(rename_all = "camelCase")]
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
host: String::new(),
|
||||||
|
port: None,
|
||||||
|
crt_file: None,
|
||||||
|
key_file: None,
|
||||||
|
pfx_file: None,
|
||||||
|
passphrase: None,
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
|
||||||
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
pub struct ClientCertificate {
|
pub struct ClientCertificate {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
@@ -75,13 +89,18 @@ pub struct ClientCertificate {
|
|||||||
pub pfx_file: Option<String>,
|
pub pfx_file: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub passphrase: Option<String>,
|
pub passphrase: Option<String>,
|
||||||
#[serde(default = "default_true")]
|
|
||||||
#[ts(optional, as = "Option<bool>")]
|
#[ts(optional, as = "Option<bool>")]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
|
impl Default for DnsOverride {
|
||||||
#[serde(rename_all = "camelCase")]
|
fn default() -> Self {
|
||||||
|
Self { hostname: String::new(), ipv4: Vec::new(), ipv6: Vec::new(), enabled: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
|
||||||
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
pub struct DnsOverride {
|
pub struct DnsOverride {
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
@@ -89,7 +108,6 @@ pub struct DnsOverride {
|
|||||||
pub ipv4: Vec<String>,
|
pub ipv4: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ipv6: Vec<String>,
|
pub ipv6: Vec<String>,
|
||||||
#[serde(default = "default_true")]
|
|
||||||
#[ts(optional, as = "Option<bool>")]
|
#[ts(optional, as = "Option<bool>")]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
}
|
}
|
||||||
@@ -211,7 +229,6 @@ pub struct InheritedBoolSetting {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[ts(optional, as = "Option<bool>")]
|
#[ts(optional, as = "Option<bool>")]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
#[serde(default = "default_true")]
|
|
||||||
pub value: bool,
|
pub value: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +464,31 @@ impl UpsertModelInfo for Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
|
impl Default for Workspace {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
model: "workspace".to_string(),
|
||||||
|
id: String::new(),
|
||||||
|
created_at: NaiveDateTime::default(),
|
||||||
|
updated_at: NaiveDateTime::default(),
|
||||||
|
authentication: BTreeMap::new(),
|
||||||
|
authentication_type: None,
|
||||||
|
description: String::new(),
|
||||||
|
headers: Vec::new(),
|
||||||
|
name: String::new(),
|
||||||
|
encryption_key_challenge: None,
|
||||||
|
setting_validate_certificates: true,
|
||||||
|
setting_follow_redirects: true,
|
||||||
|
setting_request_timeout: 0,
|
||||||
|
setting_request_message_size: DEFAULT_REQUEST_MESSAGE_SIZE,
|
||||||
|
setting_dns_overrides: Vec::new(),
|
||||||
|
setting_send_cookies: true,
|
||||||
|
setting_store_cookies: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
#[enum_def(table_name = "workspaces")]
|
#[enum_def(table_name = "workspaces")]
|
||||||
@@ -467,18 +508,13 @@ pub struct Workspace {
|
|||||||
pub encryption_key_challenge: Option<String>,
|
pub encryption_key_challenge: Option<String>,
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
#[serde(default = "default_true")]
|
|
||||||
pub setting_validate_certificates: bool,
|
pub setting_validate_certificates: bool,
|
||||||
#[serde(default = "default_true")]
|
|
||||||
pub setting_follow_redirects: bool,
|
pub setting_follow_redirects: bool,
|
||||||
pub setting_request_timeout: i32,
|
pub setting_request_timeout: i32,
|
||||||
#[serde(default = "default_request_message_size")]
|
|
||||||
pub setting_request_message_size: i32,
|
pub setting_request_message_size: i32,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub setting_dns_overrides: Vec<DnsOverride>,
|
pub setting_dns_overrides: Vec<DnsOverride>,
|
||||||
#[serde(default = "default_true")]
|
|
||||||
pub setting_send_cookies: bool,
|
pub setting_send_cookies: bool,
|
||||||
#[serde(default = "default_true")]
|
|
||||||
pub setting_store_cookies: bool,
|
pub setting_store_cookies: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -984,11 +1020,16 @@ impl UpsertModelInfo for Environment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
|
impl Default for EnvironmentVariable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { enabled: true, name: String::new(), value: String::new(), id: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
pub struct EnvironmentVariable {
|
pub struct EnvironmentVariable {
|
||||||
#[serde(default = "default_true")]
|
|
||||||
#[ts(optional, as = "Option<bool>")]
|
#[ts(optional, as = "Option<bool>")]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -1013,7 +1054,35 @@ pub struct ParentHeaders {
|
|||||||
pub headers: Vec<HttpRequestHeader>,
|
pub headers: Vec<HttpRequestHeader>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
|
impl Default for Folder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
model: "folder".to_string(),
|
||||||
|
id: String::new(),
|
||||||
|
created_at: NaiveDateTime::default(),
|
||||||
|
updated_at: NaiveDateTime::default(),
|
||||||
|
workspace_id: String::new(),
|
||||||
|
folder_id: None,
|
||||||
|
authentication: BTreeMap::new(),
|
||||||
|
authentication_type: None,
|
||||||
|
description: String::new(),
|
||||||
|
headers: Vec::new(),
|
||||||
|
name: String::new(),
|
||||||
|
sort_priority: 0.0,
|
||||||
|
setting_send_cookies: InheritedBoolSetting::default(),
|
||||||
|
setting_store_cookies: InheritedBoolSetting::default(),
|
||||||
|
setting_validate_certificates: InheritedBoolSetting::default(),
|
||||||
|
setting_follow_redirects: InheritedBoolSetting::default(),
|
||||||
|
setting_request_timeout: InheritedIntSetting::default(),
|
||||||
|
setting_request_message_size: InheritedIntSetting {
|
||||||
|
enabled: false,
|
||||||
|
value: DEFAULT_REQUEST_MESSAGE_SIZE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
#[enum_def(table_name = "folders")]
|
#[enum_def(table_name = "folders")]
|
||||||
@@ -1038,7 +1107,6 @@ pub struct Folder {
|
|||||||
pub setting_validate_certificates: InheritedBoolSetting,
|
pub setting_validate_certificates: InheritedBoolSetting,
|
||||||
pub setting_follow_redirects: InheritedBoolSetting,
|
pub setting_follow_redirects: InheritedBoolSetting,
|
||||||
pub setting_request_timeout: InheritedIntSetting,
|
pub setting_request_timeout: InheritedIntSetting,
|
||||||
#[serde(default = "default_request_message_size_setting")]
|
|
||||||
pub setting_request_message_size: InheritedIntSetting,
|
pub setting_request_message_size: InheritedIntSetting,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1152,11 +1220,16 @@ impl UpsertModelInfo for Folder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
|
impl Default for HttpRequestHeader {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { enabled: true, name: String::new(), value: String::new(), id: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
pub struct HttpRequestHeader {
|
pub struct HttpRequestHeader {
|
||||||
#[serde(default = "default_true")]
|
|
||||||
#[ts(optional, as = "Option<bool>")]
|
#[ts(optional, as = "Option<bool>")]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -1165,11 +1238,16 @@ pub struct HttpRequestHeader {
|
|||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
|
impl Default for HttpUrlParameter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { enabled: true, name: String::new(), value: String::new(), id: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
pub struct HttpUrlParameter {
|
pub struct HttpUrlParameter {
|
||||||
#[serde(default = "default_true")]
|
|
||||||
#[ts(optional, as = "Option<bool>")]
|
#[ts(optional, as = "Option<bool>")]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
/// Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id`
|
/// Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id`
|
||||||
@@ -1180,7 +1258,36 @@ pub struct HttpUrlParameter {
|
|||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
|
impl Default for HttpRequest {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
model: "http_request".to_string(),
|
||||||
|
id: String::new(),
|
||||||
|
created_at: NaiveDateTime::default(),
|
||||||
|
updated_at: NaiveDateTime::default(),
|
||||||
|
workspace_id: String::new(),
|
||||||
|
folder_id: None,
|
||||||
|
authentication: BTreeMap::new(),
|
||||||
|
authentication_type: None,
|
||||||
|
body: BTreeMap::new(),
|
||||||
|
body_type: None,
|
||||||
|
description: String::new(),
|
||||||
|
headers: Vec::new(),
|
||||||
|
method: "GET".to_string(),
|
||||||
|
name: String::new(),
|
||||||
|
sort_priority: 0.0,
|
||||||
|
url: String::new(),
|
||||||
|
url_parameters: Vec::new(),
|
||||||
|
setting_send_cookies: InheritedBoolSetting::default(),
|
||||||
|
setting_store_cookies: InheritedBoolSetting::default(),
|
||||||
|
setting_validate_certificates: InheritedBoolSetting::default(),
|
||||||
|
setting_follow_redirects: InheritedBoolSetting::default(),
|
||||||
|
setting_request_timeout: InheritedIntSetting::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
#[enum_def(table_name = "http_requests")]
|
#[enum_def(table_name = "http_requests")]
|
||||||
@@ -1201,7 +1308,6 @@ pub struct HttpRequest {
|
|||||||
pub body_type: Option<String>,
|
pub body_type: Option<String>,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub headers: Vec<HttpRequestHeader>,
|
pub headers: Vec<HttpRequestHeader>,
|
||||||
#[serde(default = "default_http_method")]
|
|
||||||
pub method: String,
|
pub method: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub sort_priority: f64,
|
pub sort_priority: f64,
|
||||||
@@ -1457,7 +1563,36 @@ impl Default for WebsocketMessageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
|
impl Default for WebsocketRequest {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
model: "websocket_request".to_string(),
|
||||||
|
id: String::new(),
|
||||||
|
created_at: NaiveDateTime::default(),
|
||||||
|
updated_at: NaiveDateTime::default(),
|
||||||
|
workspace_id: String::new(),
|
||||||
|
folder_id: None,
|
||||||
|
authentication: BTreeMap::new(),
|
||||||
|
authentication_type: None,
|
||||||
|
description: String::new(),
|
||||||
|
headers: Vec::new(),
|
||||||
|
message: String::new(),
|
||||||
|
name: String::new(),
|
||||||
|
sort_priority: 0.0,
|
||||||
|
url: String::new(),
|
||||||
|
url_parameters: Vec::new(),
|
||||||
|
setting_send_cookies: InheritedBoolSetting::default(),
|
||||||
|
setting_store_cookies: InheritedBoolSetting::default(),
|
||||||
|
setting_validate_certificates: InheritedBoolSetting::default(),
|
||||||
|
setting_request_message_size: InheritedIntSetting {
|
||||||
|
enabled: false,
|
||||||
|
value: DEFAULT_REQUEST_MESSAGE_SIZE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
#[enum_def(table_name = "websocket_requests")]
|
#[enum_def(table_name = "websocket_requests")]
|
||||||
@@ -1484,7 +1619,6 @@ pub struct WebsocketRequest {
|
|||||||
pub setting_send_cookies: InheritedBoolSetting,
|
pub setting_send_cookies: InheritedBoolSetting,
|
||||||
pub setting_store_cookies: InheritedBoolSetting,
|
pub setting_store_cookies: InheritedBoolSetting,
|
||||||
pub setting_validate_certificates: InheritedBoolSetting,
|
pub setting_validate_certificates: InheritedBoolSetting,
|
||||||
#[serde(default = "default_request_message_size_setting")]
|
|
||||||
pub setting_request_message_size: InheritedIntSetting,
|
pub setting_request_message_size: InheritedIntSetting,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2117,7 +2251,35 @@ impl UpsertModelInfo for GraphQlIntrospection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
|
impl Default for GrpcRequest {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
model: "grpc_request".to_string(),
|
||||||
|
id: String::new(),
|
||||||
|
created_at: NaiveDateTime::default(),
|
||||||
|
updated_at: NaiveDateTime::default(),
|
||||||
|
workspace_id: String::new(),
|
||||||
|
folder_id: None,
|
||||||
|
authentication_type: None,
|
||||||
|
authentication: BTreeMap::new(),
|
||||||
|
description: String::new(),
|
||||||
|
message: String::new(),
|
||||||
|
metadata: Vec::new(),
|
||||||
|
method: None,
|
||||||
|
name: String::new(),
|
||||||
|
service: None,
|
||||||
|
sort_priority: 0.0,
|
||||||
|
url: String::new(),
|
||||||
|
setting_validate_certificates: InheritedBoolSetting::default(),
|
||||||
|
setting_request_message_size: InheritedIntSetting {
|
||||||
|
enabled: false,
|
||||||
|
value: DEFAULT_REQUEST_MESSAGE_SIZE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
#[enum_def(table_name = "grpc_requests")]
|
#[enum_def(table_name = "grpc_requests")]
|
||||||
@@ -2143,7 +2305,6 @@ pub struct GrpcRequest {
|
|||||||
/// Server URL (http for plaintext or https for secure)
|
/// Server URL (http for plaintext or https for secure)
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub setting_validate_certificates: InheritedBoolSetting,
|
pub setting_validate_certificates: InheritedBoolSetting,
|
||||||
#[serde(default = "default_request_message_size_setting")]
|
|
||||||
pub setting_request_message_size: InheritedIntSetting,
|
pub setting_request_message_size: InheritedIntSetting,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2794,22 +2955,12 @@ impl<'s> TryFrom<&Row<'s>> for PluginKeyValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_true() -> bool {
|
/// Only used as a `from_row` fallback for an unparseable settings column. The
|
||||||
true
|
/// value a *new* model gets comes from that model's `Default` impl.
|
||||||
}
|
|
||||||
|
|
||||||
fn default_request_message_size() -> i32 {
|
|
||||||
DEFAULT_REQUEST_MESSAGE_SIZE
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_request_message_size_setting() -> InheritedIntSetting {
|
fn default_request_message_size_setting() -> InheritedIntSetting {
|
||||||
InheritedIntSetting { enabled: false, value: DEFAULT_REQUEST_MESSAGE_SIZE }
|
InheritedIntSetting { enabled: false, value: DEFAULT_REQUEST_MESSAGE_SIZE }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_http_method() -> String {
|
|
||||||
"GET".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! define_any_model {
|
macro_rules! define_any_model {
|
||||||
($($type:ident),* $(,)?) => {
|
($($type:ident),* $(,)?) => {
|
||||||
@@ -2953,3 +3104,65 @@ impl AnyModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Every model below carries `#[serde(default)]` at the container level, so a
|
||||||
|
/// missing key is filled from `Default::default()`, which makes each `Default`
|
||||||
|
/// impl the single definition of that model's defaults.
|
||||||
|
///
|
||||||
|
/// Deserializing `{}` therefore equals `Default::default()` by construction
|
||||||
|
/// today. What this catches is the two ways that can come apart again, both of
|
||||||
|
/// which have already bitten us:
|
||||||
|
///
|
||||||
|
/// 1. A field-level `#[serde(default = "...")]` (or bare `#[serde(default)]`)
|
||||||
|
/// added back on a field whose `Default` says something else. That is exactly
|
||||||
|
/// the shape of the bug this replaced: `setting_send_cookies` deserialized as
|
||||||
|
/// true but a derived `Default` produced false, so the bootstrapped workspace
|
||||||
|
/// silently sent no cookies.
|
||||||
|
/// 2. The container-level `#[serde(default)]` being dropped, which turns every
|
||||||
|
/// missing key into a deserialization error instead.
|
||||||
|
macro_rules! assert_default_matches_serde {
|
||||||
|
($($t:ty),+ $(,)?) => {
|
||||||
|
$(
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::from_str::<$t>("{}").expect(concat!(
|
||||||
|
stringify!($t),
|
||||||
|
" must deserialize from an empty object"
|
||||||
|
)),
|
||||||
|
<$t>::default(),
|
||||||
|
concat!(stringify!($t), ": Default::default() disagrees with its serde defaults"),
|
||||||
|
);
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn defaults_match_serde_defaults() {
|
||||||
|
assert_default_matches_serde!(
|
||||||
|
Workspace,
|
||||||
|
HttpRequest,
|
||||||
|
Folder,
|
||||||
|
GrpcRequest,
|
||||||
|
WebsocketRequest,
|
||||||
|
HttpRequestHeader,
|
||||||
|
HttpUrlParameter,
|
||||||
|
EnvironmentVariable,
|
||||||
|
DnsOverride,
|
||||||
|
ClientCertificate,
|
||||||
|
InheritedBoolSetting,
|
||||||
|
InheritedIntSetting,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn defaults_carry_their_model_name() {
|
||||||
|
assert_eq!(Workspace::default().model, "workspace");
|
||||||
|
assert_eq!(HttpRequest::default().model, "http_request");
|
||||||
|
assert_eq!(Folder::default().model, "folder");
|
||||||
|
assert_eq!(GrpcRequest::default().model, "grpc_request");
|
||||||
|
assert_eq!(WebsocketRequest::default().model, "websocket_request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,13 +25,7 @@ impl<'a> ClientDb<'a> {
|
|||||||
|
|
||||||
if workspaces.is_empty() {
|
if workspaces.is_empty() {
|
||||||
workspaces.push(self.upsert_workspace(
|
workspaces.push(self.upsert_workspace(
|
||||||
&Workspace {
|
&Workspace { name: "Yaak".to_string(), ..Default::default() },
|
||||||
name: "Yaak".to_string(),
|
|
||||||
setting_follow_redirects: true,
|
|
||||||
setting_request_message_size: crate::models::DEFAULT_REQUEST_MESSAGE_SIZE,
|
|
||||||
setting_validate_certificates: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&UpdateSource::Background,
|
&UpdateSource::Background,
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
@@ -194,16 +188,40 @@ impl<'a> ClientDb<'a> {
|
|||||||
pub fn default_headers() -> Vec<HttpRequestHeader> {
|
pub fn default_headers() -> Vec<HttpRequestHeader> {
|
||||||
vec![
|
vec![
|
||||||
HttpRequestHeader {
|
HttpRequestHeader {
|
||||||
enabled: true,
|
|
||||||
name: "User-Agent".to_string(),
|
name: "User-Agent".to_string(),
|
||||||
value: "yaak".to_string(),
|
value: "yaak".to_string(),
|
||||||
id: None,
|
..Default::default()
|
||||||
},
|
},
|
||||||
HttpRequestHeader {
|
HttpRequestHeader {
|
||||||
enabled: true,
|
|
||||||
name: "Accept".to_string(),
|
name: "Accept".to_string(),
|
||||||
value: "*/*".to_string(),
|
value: "*/*".to_string(),
|
||||||
id: None,
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::init_in_memory;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bootstraps_first_workspace_with_real_defaults() {
|
||||||
|
let (query_manager, _blob_manager, _rx) = init_in_memory().expect("Failed to init DB");
|
||||||
|
let db = query_manager.connect();
|
||||||
|
|
||||||
|
let workspaces = db.list_workspaces().expect("Failed to list workspaces");
|
||||||
|
let workspace = workspaces.first().expect("No workspace was bootstrapped");
|
||||||
|
|
||||||
|
// This workspace is built in Rust and never deserialized, so it only gets
|
||||||
|
// these values if `Workspace::default()` carries them. Asserted through the
|
||||||
|
// DB round trip, since the column values are what a fresh install lives with.
|
||||||
|
assert!(workspace.setting_send_cookies, "setting_send_cookies");
|
||||||
|
assert!(workspace.setting_store_cookies, "setting_store_cookies");
|
||||||
|
assert!(workspace.setting_follow_redirects, "setting_follow_redirects");
|
||||||
|
assert!(workspace.setting_validate_certificates, "setting_validate_certificates");
|
||||||
|
assert_eq!(
|
||||||
|
workspace.setting_request_message_size,
|
||||||
|
crate::models::DEFAULT_REQUEST_MESSAGE_SIZE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -704,7 +704,7 @@ export function __wbindgen_cast_0000000000000002(arg0, arg1) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
export function __wbindgen_cast_0000000000000003(arg0, arg1) {
|
export function __wbindgen_cast_0000000000000003(arg0, arg1) {
|
||||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("IDBVersionChangeEvent")], shim_idx: 114, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("IDBVersionChangeEvent")], shim_idx: 164, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hdf19cb46f9aecb24);
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hdf19cb46f9aecb24);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -361,6 +361,28 @@ fn dispatch(
|
|||||||
to_json(db.get_or_create_workspace_meta(&workspace.id).map_err(js_error)?)
|
to_json(db.get_or_create_workspace_meta(&workspace.id).map_err(js_error)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"cmd_delete_all_http_responses" => {
|
||||||
|
let req: RequestIdReq = from_js(payload)?;
|
||||||
|
host.queries
|
||||||
|
.connect()
|
||||||
|
.delete_all_http_responses_for_request(&req.request_id, source)
|
||||||
|
.map_err(js_error)?;
|
||||||
|
to_json(())
|
||||||
|
}
|
||||||
|
|
||||||
|
"cmd_delete_send_history" => {
|
||||||
|
let req: WorkspaceIdReq = from_js(payload)?;
|
||||||
|
host.queries
|
||||||
|
.with_tx(|tx| {
|
||||||
|
tx.delete_all_http_responses_for_workspace(&req.workspace_id, source)?;
|
||||||
|
tx.delete_all_grpc_connections_for_workspace(&req.workspace_id, source)?;
|
||||||
|
tx.delete_all_websocket_connections_for_workspace(&req.workspace_id, source)?;
|
||||||
|
Ok::<(), yaak_models::error::Error>(())
|
||||||
|
})
|
||||||
|
.map_err(js_error)?;
|
||||||
|
to_json(())
|
||||||
|
}
|
||||||
|
|
||||||
other => Err(js_error(format!("yaak-web: `{other}` is not a command this host answers"))),
|
other => Err(js_error(format!("yaak-web: `{other}` is not a command this host answers"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ const HANDLERS: Partial<Record<AppCmd, Handler>> = {
|
|||||||
models_grpc_events: (payload, db) => db.rpc("models_grpc_events", payload),
|
models_grpc_events: (payload, db) => db.rpc("models_grpc_events", payload),
|
||||||
models_websocket_events: (payload, db) => db.rpc("models_websocket_events", payload),
|
models_websocket_events: (payload, db) => db.rpc("models_websocket_events", payload),
|
||||||
cmd_get_workspace_meta: (payload, db) => db.rpc("cmd_get_workspace_meta", payload),
|
cmd_get_workspace_meta: (payload, db) => db.rpc("cmd_get_workspace_meta", payload),
|
||||||
|
cmd_delete_all_http_responses: (payload, db) => db.rpc("cmd_delete_all_http_responses", payload),
|
||||||
|
cmd_delete_send_history: (payload, db) => db.rpc("cmd_delete_send_history", payload),
|
||||||
|
|
||||||
/* ------------------------------- sending ------------------------------- */
|
/* ------------------------------- sending ------------------------------- */
|
||||||
|
|
||||||
@@ -302,10 +304,6 @@ const DECLINED: Partial<Record<AppCmd, [reason: string, capability: CapabilityNa
|
|||||||
cmd_call_folder_action: ["Plugins aren't available in the browser yet", "plugins"],
|
cmd_call_folder_action: ["Plugins aren't available in the browser yet", "plugins"],
|
||||||
cmd_call_http_authentication_action: ["Plugins aren't available in the browser yet", "plugins"],
|
cmd_call_http_authentication_action: ["Plugins aren't available in the browser yet", "plugins"],
|
||||||
|
|
||||||
// Sending history and its bookkeeping belong to the send slice.
|
|
||||||
cmd_delete_send_history: ["Sending isn't available in the browser yet", null],
|
|
||||||
cmd_delete_all_http_responses: ["Sending isn't available in the browser yet", null],
|
|
||||||
|
|
||||||
cmd_send_feedback: ["Feedback goes through the desktop app for now", null],
|
cmd_send_feedback: ["Feedback goes through the desktop app for now", null],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user