From 5f84938149311c4149c5e42b65b21139aede51ac Mon Sep 17 00:00:00 2001 From: mahamdan Date: Thu, 8 Dec 2022 15:16:10 +0200 Subject: [PATCH] Object query params (maps/dictionaries) support --- src/components/TryOut/FormItem.tsx | 26 +++++++++----- src/components/TryOut/FormSection.tsx | 5 ++- src/components/TryOut/SchemaSection.tsx | 11 +++++- src/components/TryOut/TryOut.tsx | 6 ++-- src/utils/tryout.ts | 47 ++++++++++++++++++++----- 5 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/components/TryOut/FormItem.tsx b/src/components/TryOut/FormItem.tsx index 2573c4659c..156349e323 100644 --- a/src/components/TryOut/FormItem.tsx +++ b/src/components/TryOut/FormItem.tsx @@ -10,7 +10,7 @@ import { ArrayForm, containerStyle } from './ArrayForm'; import { SchemaSection } from './SchemaSection'; import { Dropdown, Input, Label } from './styled.elements'; -const FormItemTypesSwitch = ({ item, onChange, discriminator, ancestors }) => { +const FormItemTypesSwitch = ({ item, onChange, discriminator, ancestors, location }) => { const { schema, name, example, description, required, kind } = item; const { oneOf, activeOneOf } = schema; const oneOfSchema = oneOf?.[activeOneOf]; @@ -23,7 +23,9 @@ const FormItemTypesSwitch = ({ item, onChange, discriminator, ancestors }) => { onChange && onChange(name, value, undefined, ancestors, item.in)} + onChange={value => + onChange && onChange(name, value, undefined, ancestors, item.in || location) + } /> ) : ( <> @@ -34,7 +36,8 @@ const FormItemTypesSwitch = ({ item, onChange, discriminator, ancestors }) => { color={'white'} backgroundColor={'#326CD1'} onChange={e => - onChange && onChange(name, e.target.files?.[0], undefined, ancestors, item.in) + onChange && + onChange(name, e.target.files?.[0], undefined, ancestors, item.in || location) } /> ) : ( @@ -42,7 +45,8 @@ const FormItemTypesSwitch = ({ item, onChange, discriminator, ancestors }) => { placeholder={`${example || description || ''}`} type={schema.format || 'text'} onChange={e => - onChange && onChange(name, e.target.value, undefined, ancestors, item.in) + onChange && + onChange(name, e.target.value, undefined, ancestors, item.in || location) } /> )} @@ -61,7 +65,7 @@ const FormItemTypesSwitch = ({ item, onChange, discriminator, ancestors }) => { !isNaN(Number(e.target.value)) ? Number(e.target.value) : e.target.value, undefined, ancestors, - item.in, + item.in || location, ) } /> @@ -78,7 +82,7 @@ const FormItemTypesSwitch = ({ item, onChange, discriminator, ancestors }) => { selectObject.target.value === 'true' ? true : false, undefined, ancestors, - item.in, + item.in || location, ); }} > @@ -100,7 +104,7 @@ const FormItemTypesSwitch = ({ item, onChange, discriminator, ancestors }) => { required={required} onChange={onChange} ancestors={ancestors} - location={item.in} + location={item.in || location} /> ); } @@ -144,7 +148,8 @@ const FormItemTypesSwitch = ({ item, onChange, discriminator, ancestors }) => { editable hideButtons setParsedJSON={jsonValue => - onChange && onChange(dictionaryName, jsonValue, undefined, ancestors) + onChange && + onChange(dictionaryName, jsonValue, undefined, ancestors, item.in || location) } /> @@ -173,6 +178,7 @@ enum FormItemType { interface FormItemProps { item: FieldModel; ancestors: string[]; + location?: string; onChange: () => void; discriminator?: { fieldName: string; @@ -181,7 +187,7 @@ interface FormItemProps { } export const FormItem = observer( - ({ item, onChange, discriminator, ancestors = [] }: FormItemProps) => { + ({ item, onChange, discriminator, ancestors = [], location }: FormItemProps) => { const { expanded, name, schema } = item; const { activeOneOf, oneOf, isCircular, isPrimitive, type } = schema; const oneOfSchema = oneOf?.[activeOneOf]; @@ -236,6 +242,7 @@ export const FormItem = observer( onChange={onChange} discriminator={discriminator} ancestors={ancestors} + location={location} /> @@ -245,6 +252,7 @@ export const FormItem = observer( schema={item.schema} onChange={onChange} ancestors={[...ancestors, name]} + location={item.in} /> )} diff --git a/src/components/TryOut/FormSection.tsx b/src/components/TryOut/FormSection.tsx index 392d0cc72e..9433bcdc83 100644 --- a/src/components/TryOut/FormSection.tsx +++ b/src/components/TryOut/FormSection.tsx @@ -18,6 +18,7 @@ const Form = styled.div` interface FormSectionProps { items: FieldModel[]; ancestors?: string[]; + location?: string; onChange: () => void; discriminator?: { fieldName: string; @@ -30,14 +31,16 @@ export const FormSection = ({ onChange, discriminator, ancestors = [], + location, }: FormSectionProps) => { return (
{items.map((item, idx) => ( diff --git a/src/components/TryOut/SchemaSection.tsx b/src/components/TryOut/SchemaSection.tsx index 47f5778a88..097cff3d79 100644 --- a/src/components/TryOut/SchemaSection.tsx +++ b/src/components/TryOut/SchemaSection.tsx @@ -18,10 +18,18 @@ interface SchemaSectionProps { ancestors?: string[]; onChange?: any; requestPayload?: any; + location?: string; } export const SchemaSection = observer( - ({ schema, contentType, onChange, ancestors = [], requestPayload }: SchemaSectionProps) => { + ({ + schema, + contentType, + onChange, + ancestors = [], + requestPayload, + location, + }: SchemaSectionProps) => { if (!schema) return null; switch (contentType) { @@ -87,6 +95,7 @@ export const SchemaSection = observer( { if (arrayIndex !== undefined) { const updatedObject = getUpdatedObject( - request.queryParams, + request.pathParams, fieldName, value, arrayIndex, @@ -187,7 +187,7 @@ export const TryOut = observer( setRequest(request => { if (arrayIndex !== undefined) { const updatedObject = getUpdatedObject( - request.queryParams, + request.cookieParams, fieldName, value, arrayIndex, @@ -212,7 +212,7 @@ export const TryOut = observer( setRequest(request => { if (arrayIndex !== undefined) { const updatedObject = getUpdatedObject( - request.queryParams, + request.header, fieldName, value, arrayIndex, diff --git a/src/utils/tryout.ts b/src/utils/tryout.ts index 6f59c15258..818c13e8f3 100644 --- a/src/utils/tryout.ts +++ b/src/utils/tryout.ts @@ -12,27 +12,54 @@ const appendPathParamsToPath = (path: string, pathParams: Record return path; }; -const appendQueryParamsToPath = (path: string, queryParams: Record): string => { - const entries = Object.entries(queryParams); - let paramsSuffix = ''; - +/** + * e.g. [ + * ["a", "b"], + * ["c", "d"] + * ] + * becomes "a=b&c=d" + */ +const entriesToQueryString = (entries): string => { + let queryString = ''; for (let i = 0; i < entries.length; i++) { const [key, value] = entries[i]; - paramsSuffix += - paramsSuffix === '' + queryString += + queryString === '' ? `${key}=${encodeURIComponent(value)}` : `&${key}=${encodeURIComponent(value)}`; } + return queryString; +}; + +const appendQueryParamsToPath = (path: string, queryParams: Record): string => { + const entries = Object.entries(queryParams); + const paramsSuffix = entriesToQueryString(entries); return paramsSuffix === '' ? path : `${path}?${paramsSuffix}`; }; +/** + * + * @returns equivalent params, with dictionary params converted to string params + * e.g. reqParam = {id: 3, severity:5} becomes reqParam=id%3D%3D3%26%26severity%3D%3D5 or reqParam=id==3&&severity==5 + */ +const formatQueryParams = params => { + if (isEmpty(params)) return params; + return Object.fromEntries( + Object.entries(params).map(([key, value]) => { + const isObjectParam = typeof value === 'object' && value !== null; + if (!isObjectParam) return [key, value]; + return [key, entriesToQueryString(Object.entries(value as object))]; + }), + ); +}; + export const appendParamsToPath = ( path: string, pathParams: Record, queryParams: Record, ): string => { path = appendPathParamsToPath(path, pathParams); - path = appendQueryParamsToPath(path, queryParams); + path = appendQueryParamsToPath(path, formatQueryParams(queryParams)); return path; }; @@ -197,7 +224,7 @@ export const getUpdatedObject = ( }; /** - * Method used for cleaning up an object from fields having empty strings as values + * Method used for cleaning up an object from fields having empty strings or empty objects as values * as those make no sense in some use cases, such as for request parameters (query, path etc). * This method esentially deletes fields that have values such as '', ' ', ' ' (i.e. empty string), * and arrays, rest of the fields are not touched whatsoever. @@ -210,7 +237,9 @@ const cleanEmptyFields = (obj: Record): Record => { if (entries.length === 0) return obj; entries.forEach(([key, value]) => { - typeof obj[key] === 'string' && isEmpty(obj[key].replace(/\s/g, '')) + const isEmptyString = typeof obj[key] === 'string' && isEmpty(obj[key].replace(/\s/g, '')); + const isEmptyObject = typeof obj[key] === 'object' && obj[key] !== null && isEmpty(obj[key]); + isEmptyString || isEmptyObject ? delete obj[key] : Array.isArray(value) ? getCleanArray(value)