Skip to content

Commit

Permalink
feat: parameter validations
Browse files Browse the repository at this point in the history
  • Loading branch information
mallachari committed Dec 11, 2020
1 parent 9a4e21c commit 96cd82e
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 23 deletions.
29 changes: 29 additions & 0 deletions packages/elements/src/__fixtures__/operations/put-todos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,35 @@ export const httpOperation: IHttpOperation = {
name: 'account-id',
style: HttpParamStyles.Simple,
required: true,
examples: [
{
value: 'example id',
key: 'example',
},
],
},
{
schema: {
type: 'string',
description: 'Your Stoplight account id',
},
name: 'message-id',
style: HttpParamStyles.Simple,
required: true,
examples: [
{
value: 'example value',
key: 'example 1',
},
{
value: 'another example',
key: 'example 2',
},
{
value: 'something else',
key: 'example 3',
},
],
},
],
path: [
Expand Down
6 changes: 4 additions & 2 deletions packages/elements/src/components/TryIt/BasicSend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as React from 'react';

import { HttpCodeDescriptions } from '../../constants';
import { getHttpCodeColor } from '../../utils/http';
import { OperationParameters } from './OperationParameters';
import { initialParameterValues, OperationParameters } from './OperationParameters';

export interface BasicSendProps {
httpOperation: IHttpOperation;
Expand All @@ -32,7 +32,9 @@ export const BasicSend: React.FC<BasicSendProps> = ({ httpOperation }) => {
headers: httpOperation.request?.headers,
};

const [parameterValues, setParameterValues] = React.useState<Dictionary<string, string>>({});
const [parameterValues, setParameterValues] = React.useState<Dictionary<string, string>>(
initialParameterValues(operationParameters),
);

if (!server) return null;

Expand Down
123 changes: 102 additions & 21 deletions packages/elements/src/components/TryIt/OperationParameters.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { Flex, Input, Panel, Text } from '@stoplight/mosaic';
import { Dictionary, IHttpHeaderParam, IHttpPathParam, IHttpQueryParam } from '@stoplight/types';
import { sortBy } from 'lodash';
import { Flex, Input, Panel, Select, Text } from '@stoplight/mosaic';
import {
Dictionary,
IHttpHeaderParam,
IHttpPathParam,
IHttpQueryParam,
INodeExample,
INodeExternalExample,
} from '@stoplight/types';
import { map, sortBy } from 'lodash';
import * as React from 'react';

interface OperationParameters {
export interface OperationParameters {
path?: IHttpPathParam[];
query?: IHttpQueryParam[];
headers?: IHttpHeaderParam[];
Expand All @@ -15,37 +22,111 @@ interface OperationParametersProps {
onChangeValues: (newValues: Dictionary<string, string>) => void;
}

const booleanOptions = [
{ label: 'Not Set', value: '' },
{ label: 'False', value: 'false' },
{ label: 'True', value: 'true' },
];

export function flattenParameters(parameters: OperationParameters) {
const pathParameters = sortBy(parameters.path ?? [], ['name']);
const queryParameters = sortBy(parameters.query ?? [], ['name']);
const headerParameters = sortBy(parameters.headers ?? [], ['name']);
return [...pathParameters, ...queryParameters, ...headerParameters];
}

export function initialParameterValues(operationParameters: OperationParameters) {
const parameters = flattenParameters(operationParameters);
const enums = parameters
.filter(p => p.schema?.enum)
.reduce((params, p) => {
if (p.schema?.enum?.length) {
return { ...params, [p.name]: String(p.schema?.enum[0]) };
} else {
return { ...params };
}
}, {});
const examples = parameters
.filter(p => Array.isArray(p.examples))
.reduce((params, p) => {
if (p.examples?.length) {
return {
...params,
[p.name]: exampleValue(p.examples[0]),
};
} else {
return { ...params };
}
}, {});

return {
// order matters - enums should be override examples
...examples,
...enums,
};
}

function exampleValue(example: INodeExample | INodeExternalExample) {
return 'value' in example
? (example as INodeExample).value
: 'externalValue' in example
? (example as INodeExternalExample).externalValue
: '';
}

export const OperationParameters: React.FC<OperationParametersProps> = ({
operationParameters,
values,
onChangeValues,
}) => {
const pathParameters = sortBy(operationParameters.path ?? [], ['name']);
const queryParameters = sortBy(operationParameters.query ?? [], ['name']);
const headerParameters = sortBy(operationParameters.headers ?? [], ['name']);
const parameters = [...pathParameters, ...queryParameters, ...headerParameters];
const parameters = flattenParameters(operationParameters);

const onChange = React.useCallback(
parameter => (e: React.FormEvent<HTMLSelectElement> | React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.currentTarget.value;
onChangeValues({ ...values, [parameter.name]: newValue });
},
[onChangeValues, values],
);

return (
<Panel id="collapse-open" defaultIsOpen>
<Panel.Titlebar>Parameters</Panel.Titlebar>
<Panel.Content className="sl-overflow-y-auto OperationParametersContent">
{parameters.map(parameter => {
const options =
parameter.schema?.type === 'boolean'
? booleanOptions
: parameter.schema?.enum !== undefined
? map(parameter.schema.enum, v => {
return Number.isNaN(Number(v)) ? String(v) : Number(v);
})
: null;
const exampleOptions =
parameter.examples?.length && parameter.examples.length > 1
? parameter.examples.map(example => ({ label: example.key, value: exampleValue(example) }))
: null;
return (
<Flex key={parameter.name} alignItems="center">
<Flex align="center" key={parameter.name}>
<Input appearance="minimal" readOnly value={parameter.name} />
<Text mx={3}>:</Text>
<Input
appearance="minimal"
flexGrow
placeholder={parameter.schema?.type as string}
type={parameter.schema?.type as string}
required
value={values[parameter.name] ?? ''}
onChange={e => {
const newValue = e.currentTarget.value;
onChangeValues({ ...values, [parameter.name]: newValue });
}}
/>
{options ? (
<Select flexGrow options={options} value={values[parameter.name]} onChange={onChange(parameter)} />
) : (
<Flex flexGrow>
<Input
style={{ paddingLeft: 15 }}
appearance="minimal"
flexGrow
placeholder={(parameter.schema?.default ?? parameter.schema?.type) as string}
type={parameter.schema?.type as string}
required
value={values[parameter.name] ?? ''}
onChange={onChange(parameter)}
/>
{exampleOptions && <Select flexGrow options={exampleOptions} onChange={onChange(parameter)} />}
</Flex>
)}
</Flex>
);
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { httpOperation } from '../../../__fixtures__/operations/put-todos';
import { initialParameterValues } from '../OperationParameters';

describe('Parameters', () => {
it('should fill initial parameters', () => {
const operationParameters = {
path: httpOperation.request?.path,
query: httpOperation.request?.query,
headers: httpOperation.request?.headers,
};

const parameters = initialParameterValues(operationParameters);

expect(parameters).toMatchObject({
limit: '0',
type: 'something',
value: '0',
'account-id': 'example id',
'message-id': 'example value',
});
});
});

0 comments on commit 96cd82e

Please sign in to comment.