Request body is now an object

This commit is contained in:
Gregory Schier
2023-11-12 11:16:12 -08:00
parent f1fc57830d
commit 5fd16b922c
16 changed files with 73 additions and 54 deletions

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "\n SELECT\n id,\n model,\n workspace_id,\n folder_id,\n created_at,\n updated_at,\n name,\n url,\n method,\n body,\n body_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n authentication_type,\n sort_priority,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE workspace_id = ?\n ", "query": "\n SELECT\n id,\n model,\n workspace_id,\n folder_id,\n created_at,\n updated_at,\n name,\n url,\n method,\n body AS \"body!: Json<HashMap<String, JsonValue>>\",\n body_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n authentication_type,\n sort_priority,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE workspace_id = ?\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -49,7 +49,7 @@
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "body", "name": "body!: Json<HashMap<String, JsonValue>>",
"ordinal": 9, "ordinal": 9,
"type_info": "Text" "type_info": "Text"
}, },
@@ -92,7 +92,7 @@
false, false,
false, false,
false, false,
true, false,
true, true,
false, false,
true, true,
@@ -100,5 +100,5 @@
false false
] ]
}, },
"hash": "a558e182f40286fe52bed5f03b2dc367b7229ab6bd9cda0a7ce219a438ccd5fd" "hash": "55e4e8b66c18f85d17ada00b302720e5dcd35ec4288006e4a556448d59b63952"
} }

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "\n SELECT\n id,\n model,\n workspace_id,\n folder_id,\n created_at,\n updated_at,\n name,\n url,\n method,\n body,\n body_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n authentication_type,\n sort_priority,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE id = ?\n ", "query": "\n SELECT\n id,\n model,\n workspace_id,\n folder_id,\n created_at,\n updated_at,\n name,\n url,\n method,\n body AS \"body!: Json<HashMap<String, JsonValue>>\",\n body_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n authentication_type,\n sort_priority,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE id = ?\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -49,7 +49,7 @@
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "body", "name": "body!: Json<HashMap<String, JsonValue>>",
"ordinal": 9, "ordinal": 9,
"type_info": "Text" "type_info": "Text"
}, },
@@ -92,7 +92,7 @@
false, false,
false, false,
false, false,
true, false,
true, true,
false, false,
true, true,
@@ -100,5 +100,5 @@
false false
] ]
}, },
"hash": "854536c80af3f86bb9a63b8ce059ad724374b545cb23481bb3b2ce07d7414220" "hash": "ae31827b9576ffba83a9de05e30688df3c83e145860f8dd608410a9a9254659d"
} }

View File

@@ -0,0 +1,16 @@
-- Rename old column to backup name
ALTER TABLE http_requests
RENAME COLUMN body TO body_old;
-- Create desired new body column
ALTER TABLE http_requests
ADD COLUMN body TEXT NOT NULL DEFAULT '{}';
-- Copy data from old to new body, in new JSON format
UPDATE http_requests
SET body = CASE WHEN body_old IS NULL THEN '{}' ELSE JSON_OBJECT('text', body_old) END
WHERE TRUE;
-- Drop old column
ALTER TABLE http_requests
DROP COLUMN body_old;

View File

@@ -17,7 +17,7 @@ use base64::Engine;
use fern::colors::ColoredLevelConfig; use fern::colors::ColoredLevelConfig;
use http::header::{HeaderName, ACCEPT, USER_AGENT}; use http::header::{HeaderName, ACCEPT, USER_AGENT};
use http::{HeaderMap, HeaderValue, Method}; use http::{HeaderMap, HeaderValue, Method};
use log::info; use log::{info, warn};
use rand::random; use rand::random;
use reqwest::redirect::Policy; use reqwest::redirect::Policy;
use serde::Serialize; use serde::Serialize;
@@ -182,17 +182,23 @@ async fn actually_send_request(
let m = Method::from_bytes(request.method.to_uppercase().as_bytes()) let m = Method::from_bytes(request.method.to_uppercase().as_bytes())
.expect("Failed to create method"); .expect("Failed to create method");
let builder = client.request(m, url_string.to_string()).headers(headers);
let sendable_req_result = match (request.body, request.body_type) { let mut request_builder = client.request(m, url_string.to_string()).headers(headers);
(Some(raw_body), Some(_)) => {
let body = render::render(&raw_body, &workspace, environment_ref); if let Some(t) = &request.body_type {
builder.body(body).build() let empty_value = &serde_json::to_value("").unwrap();
let b = request.body.0;
if t == "basic" {
let raw_text = b.get("text").unwrap_or(empty_value).as_str().unwrap_or("");
let body = render::render(raw_text, &workspace, environment_ref);
request_builder = request_builder.body(body);
} else {
warn!("Unsupported body type: {}", t);
} }
_ => builder.build(), }
};
let sendable_req = match sendable_req_result { let sendable_req = match request_builder.build() {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return response_err(response, e.to_string(), app_handle, pool).await; return response_err(response, e.to_string(), app_handle, pool).await;
@@ -648,9 +654,11 @@ async fn list_requests(
db_instance: State<'_, Mutex<Pool<Sqlite>>>, db_instance: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<Vec<models::HttpRequest>, String> { ) -> Result<Vec<models::HttpRequest>, String> {
let pool = &*db_instance.lock().await; let pool = &*db_instance.lock().await;
models::find_requests(workspace_id, pool) let requests = models::find_requests(workspace_id, pool)
.await .await
.map_err(|e| e.to_string()) .expect("Failed to find requests");
// .map_err(|e| e.to_string())
Ok(requests)
} }
#[tauri::command] #[tauri::command]

View File

@@ -72,7 +72,7 @@ pub struct HttpRequest {
pub url: String, pub url: String,
#[serde(default = "default_http_request_method")] #[serde(default = "default_http_request_method")]
pub method: String, pub method: String,
pub body: Option<String>, pub body: Json<HashMap<String, JsonValue>>,
pub body_type: Option<String>, pub body_type: Option<String>,
pub authentication: Json<HashMap<String, JsonValue>>, pub authentication: Json<HashMap<String, JsonValue>>,
pub authentication_type: Option<String>, pub authentication_type: Option<String>,
@@ -497,7 +497,7 @@ pub async fn find_requests(
name, name,
url, url,
method, method,
body, body AS "body!: Json<HashMap<String, JsonValue>>",
body_type, body_type,
authentication AS "authentication!: Json<HashMap<String, JsonValue>>", authentication AS "authentication!: Json<HashMap<String, JsonValue>>",
authentication_type, authentication_type,
@@ -526,7 +526,7 @@ pub async fn get_request(id: &str, pool: &Pool<Sqlite>) -> Result<HttpRequest, s
name, name,
url, url,
method, method,
body, body AS "body!: Json<HashMap<String, JsonValue>>",
body_type, body_type,
authentication AS "authentication!: Json<HashMap<String, JsonValue>>", authentication AS "authentication!: Json<HashMap<String, JsonValue>>",
authentication_type, authentication_type,

View File

@@ -1,4 +1,5 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { MotionConfig } from 'framer-motion'; import { MotionConfig } from 'framer-motion';
import { Suspense } from 'react'; import { Suspense } from 'react';
import { DndProvider } from 'react-dnd'; import { DndProvider } from 'react-dnd';
@@ -23,10 +24,10 @@ export function App() {
<MotionConfig transition={{ duration: 0.1 }}> <MotionConfig transition={{ duration: 0.1 }}>
<HelmetProvider> <HelmetProvider>
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<Suspense> <Suspense>
<AppRouter /> <AppRouter />
{/*<ReactQueryDevtools initialIsOpen={false} />*/} <ReactQueryDevtools initialIsOpen={false} />
</Suspense> </Suspense>
</DndProvider> </DndProvider>
</HelmetProvider> </HelmetProvider>
</MotionConfig> </MotionConfig>

View File

@@ -133,8 +133,8 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
[activeRequest, updateRequest], [activeRequest, updateRequest],
); );
const handleBodyChange = useCallback( const handleBodyTextChange = useCallback(
(body: string) => updateRequest.mutate({ body }), (text: string) => updateRequest.mutate({ body: { text } }),
[updateRequest], [updateRequest],
); );
const handleHeadersChange = useCallback( const handleHeadersChange = useCallback(
@@ -211,9 +211,9 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
placeholder="..." placeholder="..."
className="!bg-gray-50" className="!bg-gray-50"
heightMode={fullHeight ? 'full' : 'auto'} heightMode={fullHeight ? 'full' : 'auto'}
defaultValue={activeRequest.body ?? ''} defaultValue={`${activeRequest?.body?.text}` ?? ''}
contentType="application/json" contentType="application/json"
onChange={handleBodyChange} onChange={handleBodyTextChange}
format={(v) => tryFormatJson(v)} format={(v) => tryFormatJson(v)}
/> />
) : activeRequest.bodyType === BODY_TYPE_XML ? ( ) : activeRequest.bodyType === BODY_TYPE_XML ? (
@@ -224,17 +224,17 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
placeholder="..." placeholder="..."
className="!bg-gray-50" className="!bg-gray-50"
heightMode={fullHeight ? 'full' : 'auto'} heightMode={fullHeight ? 'full' : 'auto'}
defaultValue={activeRequest.body ?? ''} defaultValue={`${activeRequest?.body?.text}` ?? ''}
contentType="text/xml" contentType="text/xml"
onChange={handleBodyChange} onChange={handleBodyTextChange}
/> />
) : activeRequest.bodyType === BODY_TYPE_GRAPHQL ? ( ) : activeRequest.bodyType === BODY_TYPE_GRAPHQL ? (
<GraphQLEditor <GraphQLEditor
forceUpdateKey={forceUpdateKey} forceUpdateKey={forceUpdateKey}
baseRequest={activeRequest} baseRequest={activeRequest}
className="!bg-gray-50" className="!bg-gray-50"
defaultValue={activeRequest?.body ?? ''} defaultValue={`${activeRequest?.body?.text}` ?? ''}
onChange={handleBodyChange} onChange={handleBodyTextChange}
/> />
) : ( ) : (
<EmptyStateText>No Body</EmptyStateText> <EmptyStateText>No Body</EmptyStateText>

View File

@@ -2,7 +2,6 @@ import { invoke } from '@tauri-apps/api';
import classNames from 'classnames'; import classNames from 'classnames';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useAlert } from '../hooks/useAlert';
import { useAppRoutes } from '../hooks/useAppRoutes'; import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace'; import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace'; import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
@@ -39,7 +38,6 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
const { appearance, toggleAppearance } = useTheme(); const { appearance, toggleAppearance } = useTheme();
const dialog = useDialog(); const dialog = useDialog();
const prompt = usePrompt(); const prompt = usePrompt();
const alert = useAlert();
const routes = useAppRoutes(); const routes = useAppRoutes();
const items: DropdownItem[] = useMemo(() => { const items: DropdownItem[] = useMemo(() => {

View File

@@ -1,4 +1,3 @@
import { w } from '@tauri-apps/api/clipboard-79413165';
import { useResponseBodyText } from '../../hooks/useResponseBodyText'; import { useResponseBodyText } from '../../hooks/useResponseBodyText';
import { useResponseContentType } from '../../hooks/useResponseContentType'; import { useResponseContentType } from '../../hooks/useResponseContentType';
import { tryFormatJson } from '../../lib/formatters'; import { tryFormatJson } from '../../lib/formatters';

View File

@@ -1,4 +1,4 @@
import { useCallback, useMemo } from 'react'; import { useMemo } from 'react';
import type { Environment } from '../lib/models'; import type { Environment } from '../lib/models';
import { useActiveEnvironmentId } from './useActiveEnvironmentId'; import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useEnvironments } from './useEnvironments'; import { useEnvironments } from './useEnvironments';
@@ -6,10 +6,5 @@ import { useEnvironments } from './useEnvironments';
export function useActiveEnvironment(): Environment | null { export function useActiveEnvironment(): Environment | null {
const id = useActiveEnvironmentId(); const id = useActiveEnvironmentId();
const environments = useEnvironments(); const environments = useEnvironments();
const environment = useMemo( return useMemo(() => environments.find((w) => w.id === id) ?? null, [environments, id]);
() => environments.find((w) => w.id === id) ?? null,
[environments, id],
);
return environment;
} }

View File

@@ -1,5 +1,4 @@
import { useCallback } from 'react'; import { useParams } from 'react-router-dom';
import { useParams, useSearchParams } from 'react-router-dom';
import type { RouteParamsRequest } from './useAppRoutes'; import type { RouteParamsRequest } from './useAppRoutes';
export function useActiveEnvironmentId(): string | null { export function useActiveEnvironmentId(): string | null {

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import type { Folder, HttpRequest } from '../lib/models'; import type { Folder } from '../lib/models';
import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import { useActiveWorkspaceId } from './useActiveWorkspaceId';
export function foldersQueryKey({ workspaceId }: { workspaceId: string }) { export function foldersQueryKey({ workspaceId }: { workspaceId: string }) {

View File

@@ -32,7 +32,11 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
const fetchIntrospection = async () => { const fetchIntrospection = async () => {
setIsLoading(true); setIsLoading(true);
setError(undefined); setError(undefined);
const args = { ...baseRequest, body: introspectionRequestBody }; const args = {
...baseRequest,
bodyType: 'graphql',
body: { text: introspectionRequestBody },
};
const response = await minPromiseMillis(sendEphemeralRequest(args, activeEnvironmentId), 700); const response = await minPromiseMillis(sendEphemeralRequest(args, activeEnvironmentId), 700);
if (response.error) { if (response.error) {

View File

@@ -1,8 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import type { Folder, HttpRequest } from '../lib/models'; import type { Folder } from '../lib/models';
import { getFolder, getRequest } from '../lib/store'; import { getFolder } from '../lib/store';
import { requestsQueryKey } from './useRequests';
import { foldersQueryKey } from './useFolders'; import { foldersQueryKey } from './useFolders';
export function useUpdateAnyFolder() { export function useUpdateAnyFolder() {

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import type { Variable } from '../lib/models'; import type { EnvironmentVariable } from '../lib/models';
export function variablesQueryKey({ environmentId }: { environmentId: string }) { export function variablesQueryKey({ environmentId }: { environmentId: string }) {
return ['variables', { environmentId }]; return ['variables', { environmentId }];
@@ -11,7 +11,7 @@ export function useVariables({ environmentId }: { environmentId: string }) {
useQuery({ useQuery({
queryKey: variablesQueryKey({ environmentId }), queryKey: variablesQueryKey({ environmentId }),
queryFn: async () => { queryFn: async () => {
return (await invoke('list_variables', { environmentId })) as Variable[]; return (await invoke('list_variables', { environmentId })) as EnvironmentVariable[];
}, },
}).data ?? [] }).data ?? []
); );

View File

@@ -56,7 +56,7 @@ export interface HttpRequest extends BaseModel {
sortPriority: number; sortPriority: number;
name: string; name: string;
url: string; url: string;
body: string | null; body: Record<string, string | number | boolean | null | undefined>;
bodyType: string | null; bodyType: string | null;
authentication: Record<string, string | number | boolean | null | undefined>; authentication: Record<string, string | number | boolean | null | undefined>;
authenticationType: string | null; authenticationType: string | null;