mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 20:00:29 +01: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;
|
||||
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");
|
||||
request_builder = request_builder.body(body.to_owned());
|
||||
} 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 {
|
||||
return url.to_string();
|
||||
}
|
||||
|
||||
|
||||
if !p.name.starts_with(":") {
|
||||
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 { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
|
||||
import { tryFormatJson } from '../lib/formatters';
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import { Button } from './core/Button';
|
||||
import type { EditorProps } from './core/Editor';
|
||||
import { Editor, formatGraphQL } from './core/Editor';
|
||||
import { FormattedError } from './core/FormattedError';
|
||||
import { Separator } from './core/Separator';
|
||||
import { useDialog } from './DialogContext';
|
||||
import { updateSchema } from 'cm6-graphql';
|
||||
|
||||
type Props = Pick<
|
||||
EditorProps,
|
||||
'heightMode' | 'onChange' | 'defaultValue' | 'className' | 'forceUpdateKey'
|
||||
> & {
|
||||
type Props = Pick<EditorProps, 'heightMode' | 'className' | 'forceUpdateKey'> & {
|
||||
baseRequest: HttpRequest;
|
||||
onChange: (body: HttpRequest['body']) => void;
|
||||
body: HttpRequest['body'];
|
||||
};
|
||||
|
||||
interface GraphQLBody {
|
||||
query: string;
|
||||
variables?: Record<string, string | number | boolean | null>;
|
||||
operationName?: string;
|
||||
}
|
||||
|
||||
export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEditorProps }: Props) {
|
||||
export function GraphQLEditor({ body, onChange, baseRequest, ...extraEditorProps }: Props) {
|
||||
const editorViewRef = useRef<EditorView>(null);
|
||||
const { schema, isLoading, error, refetch } = useIntrospectGraphQL(baseRequest);
|
||||
const { query, variables } = useMemo<GraphQLBody>(() => {
|
||||
if (defaultValue === undefined) {
|
||||
return { query: '', variables: {} };
|
||||
const [currentBody, setCurrentBody] = useState<{ query: string; variables: string }>(() => {
|
||||
// Migrate text bodies to GraphQL format
|
||||
// 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(
|
||||
(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],
|
||||
);
|
||||
return { query: body.query ?? '', variables: body.variables ?? '' };
|
||||
});
|
||||
|
||||
const handleChangeQuery = useCallback(
|
||||
(query: string) => handleChange({ query, variables }),
|
||||
[handleChange, variables],
|
||||
);
|
||||
const handleChangeQuery = (query: string) => {
|
||||
const newBody = { query, variables: currentBody.variables };
|
||||
setCurrentBody(newBody);
|
||||
onChange(newBody);
|
||||
};
|
||||
|
||||
const handleChangeVariables = useCallback(
|
||||
(variables: string) => {
|
||||
try {
|
||||
handleChange({ query, variables: JSON.parse(variables || '{}') });
|
||||
// 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],
|
||||
);
|
||||
const handleChangeVariables = (variables: string) => {
|
||||
const newBody = { query: currentBody.query, variables };
|
||||
setCurrentBody(newBody);
|
||||
onChange(newBody);
|
||||
};
|
||||
|
||||
// Refetch the schema when the URL changes
|
||||
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]">
|
||||
<Editor
|
||||
language="graphql"
|
||||
defaultValue={query ?? ''}
|
||||
format={formatGraphQL}
|
||||
heightMode="auto"
|
||||
format={formatGraphQL}
|
||||
defaultValue={currentBody.query}
|
||||
onChange={handleChangeQuery}
|
||||
placeholder="..."
|
||||
ref={editorViewRef}
|
||||
@@ -148,8 +119,8 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
||||
<Editor
|
||||
format={tryFormatJson}
|
||||
language="json"
|
||||
defaultValue={JSON.stringify(variables, null, 2)}
|
||||
heightMode="auto"
|
||||
defaultValue={currentBody.variables}
|
||||
onChange={handleChangeVariables}
|
||||
placeholder="{}"
|
||||
useTemplating
|
||||
@@ -160,3 +131,12 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
||||
</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
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
baseRequest={activeRequest}
|
||||
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||
onChange={handleBodyTextChange}
|
||||
body={activeRequest.body}
|
||||
onChange={handleBodyChange}
|
||||
/>
|
||||
) : activeRequest.bodyType === BODY_TYPE_FORM_URLENCODED ? (
|
||||
<FormUrlencodedEditor
|
||||
|
||||
Reference in New Issue
Block a user