mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 00:58:32 +02:00
Preserve invalid GraphQL variables (#111)
This commit is contained in:
@@ -225,7 +225,22 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
|
|
||||||
let request_body = rendered_request.body;
|
let request_body = rendered_request.body;
|
||||||
if let Some(body_type) = &rendered_request.body_type {
|
if let Some(body_type) = &rendered_request.body_type {
|
||||||
if request_body.contains_key("text") {
|
if request_body.contains_key("query") && request_body.contains_key("variables") {
|
||||||
|
let query = get_str_h(&request_body, "query");
|
||||||
|
let variables = get_str_h(&request_body, "variables");
|
||||||
|
let body = if variables.trim().is_empty() {
|
||||||
|
format!(
|
||||||
|
r#"{{"query":{}}}"#,
|
||||||
|
serde_json::to_string(query).unwrap_or_default()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
r#"{{"query":{},"variables":{variables}}}"#,
|
||||||
|
serde_json::to_string(query).unwrap_or_default()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
request_builder = request_builder.body(body.to_owned());
|
||||||
|
} else if request_body.contains_key("text") {
|
||||||
let body = get_str_h(&request_body, "text");
|
let body = get_str_h(&request_body, "text");
|
||||||
request_builder = request_builder.body(body.to_owned());
|
request_builder = request_builder.body(body.to_owned());
|
||||||
} else if body_type == "application/x-www-form-urlencoded"
|
} else if body_type == "application/x-www-form-urlencoded"
|
||||||
@@ -498,7 +513,7 @@ fn replace_path_placeholder(p: &HttpUrlParameter, url: &str) -> String {
|
|||||||
if !p.enabled {
|
if !p.enabled {
|
||||||
return url.to_string();
|
return url.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.name.starts_with(":") {
|
if !p.name.starts_with(":") {
|
||||||
return url.to_string();
|
return url.to_string();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +1,48 @@
|
|||||||
|
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||||
|
import { updateSchema } from 'cm6-graphql';
|
||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
|
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
import { tryFormatJson } from '../lib/formatters';
|
||||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import type { EditorProps } from './core/Editor';
|
import type { EditorProps } from './core/Editor';
|
||||||
import { Editor, formatGraphQL } from './core/Editor';
|
import { Editor, formatGraphQL } from './core/Editor';
|
||||||
import { FormattedError } from './core/FormattedError';
|
import { FormattedError } from './core/FormattedError';
|
||||||
import { Separator } from './core/Separator';
|
import { Separator } from './core/Separator';
|
||||||
import { useDialog } from './DialogContext';
|
import { useDialog } from './DialogContext';
|
||||||
import { updateSchema } from 'cm6-graphql';
|
|
||||||
|
|
||||||
type Props = Pick<
|
type Props = Pick<EditorProps, 'heightMode' | 'className' | 'forceUpdateKey'> & {
|
||||||
EditorProps,
|
|
||||||
'heightMode' | 'onChange' | 'defaultValue' | 'className' | 'forceUpdateKey'
|
|
||||||
> & {
|
|
||||||
baseRequest: HttpRequest;
|
baseRequest: HttpRequest;
|
||||||
|
onChange: (body: HttpRequest['body']) => void;
|
||||||
|
body: HttpRequest['body'];
|
||||||
};
|
};
|
||||||
|
|
||||||
interface GraphQLBody {
|
export function GraphQLEditor({ body, onChange, baseRequest, ...extraEditorProps }: Props) {
|
||||||
query: string;
|
|
||||||
variables?: Record<string, string | number | boolean | null>;
|
|
||||||
operationName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEditorProps }: Props) {
|
|
||||||
const editorViewRef = useRef<EditorView>(null);
|
const editorViewRef = useRef<EditorView>(null);
|
||||||
const { schema, isLoading, error, refetch } = useIntrospectGraphQL(baseRequest);
|
const { schema, isLoading, error, refetch } = useIntrospectGraphQL(baseRequest);
|
||||||
const { query, variables } = useMemo<GraphQLBody>(() => {
|
const [currentBody, setCurrentBody] = useState<{ query: string; variables: string }>(() => {
|
||||||
if (defaultValue === undefined) {
|
// Migrate text bodies to GraphQL format
|
||||||
return { query: '', variables: {} };
|
// NOTE: This is how GraphQL used to be stored
|
||||||
|
if ('text' in body) {
|
||||||
|
const b = tryParseJson(body.text, {});
|
||||||
|
const variables = JSON.stringify(b.variables ?? '', null, 2);
|
||||||
|
return { query: b.query ?? '', variables };
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
const p = JSON.parse(defaultValue || '{}');
|
|
||||||
const query = p.query ?? '';
|
|
||||||
const variables = p.variables;
|
|
||||||
const operationName = p.operationName;
|
|
||||||
return { query, variables, operationName };
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
} catch (err) {
|
|
||||||
return { query: '' };
|
|
||||||
}
|
|
||||||
}, [defaultValue]);
|
|
||||||
|
|
||||||
const handleChange = useCallback(
|
return { query: body.query ?? '', variables: body.variables ?? '' };
|
||||||
(b: GraphQLBody) => {
|
});
|
||||||
try {
|
|
||||||
onChange?.(JSON.stringify(b, null, 2));
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
} catch (err) {
|
|
||||||
// Meh, not much we can do here
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleChangeQuery = useCallback(
|
const handleChangeQuery = (query: string) => {
|
||||||
(query: string) => handleChange({ query, variables }),
|
const newBody = { query, variables: currentBody.variables };
|
||||||
[handleChange, variables],
|
setCurrentBody(newBody);
|
||||||
);
|
onChange(newBody);
|
||||||
|
};
|
||||||
|
|
||||||
const handleChangeVariables = useCallback(
|
const handleChangeVariables = (variables: string) => {
|
||||||
(variables: string) => {
|
const newBody = { query: currentBody.query, variables };
|
||||||
try {
|
setCurrentBody(newBody);
|
||||||
handleChange({ query, variables: JSON.parse(variables || '{}') });
|
onChange(newBody);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
};
|
||||||
} catch (err) {
|
|
||||||
// Don't do anything if invalid JSON. The user probably hasn't finished
|
|
||||||
// typing yet.
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleChange, query],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Refetch the schema when the URL changes
|
// Refetch the schema when the URL changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -132,9 +103,9 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
|||||||
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto]">
|
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto]">
|
||||||
<Editor
|
<Editor
|
||||||
language="graphql"
|
language="graphql"
|
||||||
defaultValue={query ?? ''}
|
|
||||||
format={formatGraphQL}
|
|
||||||
heightMode="auto"
|
heightMode="auto"
|
||||||
|
format={formatGraphQL}
|
||||||
|
defaultValue={currentBody.query}
|
||||||
onChange={handleChangeQuery}
|
onChange={handleChangeQuery}
|
||||||
placeholder="..."
|
placeholder="..."
|
||||||
ref={editorViewRef}
|
ref={editorViewRef}
|
||||||
@@ -148,8 +119,8 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
|||||||
<Editor
|
<Editor
|
||||||
format={tryFormatJson}
|
format={tryFormatJson}
|
||||||
language="json"
|
language="json"
|
||||||
defaultValue={JSON.stringify(variables, null, 2)}
|
|
||||||
heightMode="auto"
|
heightMode="auto"
|
||||||
|
defaultValue={currentBody.variables}
|
||||||
onChange={handleChangeVariables}
|
onChange={handleChangeVariables}
|
||||||
placeholder="{}"
|
placeholder="{}"
|
||||||
useTemplating
|
useTemplating
|
||||||
@@ -160,3 +131,12 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tryParseJson(text: string, fallback: unknown) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
} catch (err) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -419,8 +419,8 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
<GraphQLEditor
|
<GraphQLEditor
|
||||||
forceUpdateKey={forceUpdateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
baseRequest={activeRequest}
|
baseRequest={activeRequest}
|
||||||
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
body={activeRequest.body}
|
||||||
onChange={handleBodyTextChange}
|
onChange={handleBodyChange}
|
||||||
/>
|
/>
|
||||||
) : activeRequest.bodyType === BODY_TYPE_FORM_URLENCODED ? (
|
) : activeRequest.bodyType === BODY_TYPE_FORM_URLENCODED ? (
|
||||||
<FormUrlencodedEditor
|
<FormUrlencodedEditor
|
||||||
|
|||||||
Reference in New Issue
Block a user