Rework path handling

This commit is contained in:
Malte Teichert
2024-06-09 02:16:27 +02:00
parent f035b17c36
commit b09f2dff5b
5 changed files with 269 additions and 299 deletions

View File

@@ -3,122 +3,57 @@
import { SlideToggle } from '@skeletonlabs/skeleton';
import ExampleInput from '$lib/components/atoms/ExampleInput.svelte';
export let variableName: string;
export let value: OpenAPIV3.ParameterObject;
export let location: 'path' | 'query' | 'header' | 'cookie';
export let parameter: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject;
value.name = variableName;
value.in = location;
if (location === 'path') value.required = true;
let multipleExamples = value.examples && Object.keys(value.examples).length > 1;
// if the parameter is a reference, we need to link to the reference
let isReference = '$ref' in parameter;
const addParameterExample = () => {
const exampleName = prompt('Enter a name for the example');
if (!exampleName) return;
// @ts-expect-error - working we are setting a new key
value.examples[exampleName] = {
$ref: '',
summary: '',
description: '',
value: '',
externalValue: ''
};
};
parameter = isReference
? (parameter as OpenAPIV3.ReferenceObject)
: (parameter as OpenAPIV3.ParameterObject);
// if the parameter is a path parameter, we need to limit the editing options
// @ts-expect-error parameter.in has to exist if the parameter is not a reference
let isPathParameter = !isReference && parameter.in === 'path';
</script>
<div class="card py-6 px-4 flex flex-col gap-4 text-sm">
<h4 class="h4">&lbrace;{variableName}&rbrace;</h4>
<span class="flex items-center gap-2">
<p>Location:</p>
<select
name="location"
class="select w-min"
disabled={location === 'path'}
bind:value={location}
>
<option value="path">Path</option>
<option value="query">Query</option>
<option value="header">Header</option>
<option value="cookie">Cookie</option>
</select>
</span>
<label class="space-y-2">
<p>Description</p>
<textarea
class="textarea"
bind:value={value.description}
placeholder="Description of the parameter. Supports markdown."
/>
</label>
<div class="flex flex-row gap-16">
<SlideToggle name="required" disabled={location === 'path'} bind:checked={value.required}>
Required
</SlideToggle>
<SlideToggle name="deprecated" bind:checked={value.deprecated} disabled={location === 'path'}>
Deprecated
</SlideToggle>
<SlideToggle
name="allowEmptyValue"
bind:value={value.allowEmptyValue}
disabled={location === 'path'}
>
Allow Empty Value
</SlideToggle>
</div>
<label class="space-y-2">
<p>Style</p>
<select name="style" class="select" bind:value={value.style}>
<option value={undefined} selected>none</option>
<option value="matrix">Matrix</option>
<option value="label">Label</option>
<option value="form">Form</option>
<option value="simple">Simple</option>
<option value="spaceDelimited">Space Delimited</option>
<option value="pipeDelimited">Pipe Delimited</option>
<option value="deepObject">Deep Object</option>
</select>
</label>
<div class="flex flex-row gap-16">
<SlideToggle name="explode" bind:checked={value.explode}>Explode</SlideToggle>
<SlideToggle name="allowReserved" bind:checked={value.allowReserved}>Allow Reserved</SlideToggle
>
</div>
<label class="space-y-2">
<p>Schema</p>
<!-- Subject to change -->
<select class="select" name="schema" bind:value={value.schema}>
<option value="schema">Schema not yet implemented</option>
</select>
</label>
<SlideToggle
name="multiExample"
bind:checked={multipleExamples}
on:click={() => {
if (!value.examples) value.examples = {};
}}
>
Multiple Examples?
</SlideToggle>
{#if multipleExamples}
<div class="space-y-2">
<p>Examples</p>
{#each Object.keys(value.examples) as example}
<ExampleInput bind:example={value.examples[example]} name={example} />
{/each}
<button
type="button"
class="btn btn-sm variant-filled-primary"
on:click={addParameterExample}
>
Add Example
</button>
</div>
{#if isReference}
<p class="text-xs text-error">This parameter is a reference and cannot be edited here.</p>
{:else}
<label class="space-y-2">
<p>Example</p>
<input type="text" class="input" name="example" placeholder="An example of the value." />
</label>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
{#if isPathParameter}
<p class="h3 font-bold">{parameter.name}</p>
{:else}
<label>
<p>Name</p>
<input
type="text"
name="name"
class="input"
placeholder="Name"
bind:value={parameter.name}
/>
</label>
{/if}
<label>
<p>Location</p>
<select name="in" class="select" bind:value={parameter.in} disabled={isPathParameter}>
<option value="path">Path</option>
<option value="query">Query</option>
<option value="header">Header</option>
<option value="cookie">Cookie</option>
</select>
</label>
<label>
<p>Description</p>
<textarea
name="description"
class="textarea"
bind:value={parameter.description}
placeholder="Describe the parameter. Supports Markdown."
/>
</label>
</div>
{/if}
</div>

View File

@@ -84,53 +84,57 @@
</div>
</svelte:fragment>
<svelte:fragment slot="content">
<table class="table my-2">
<tbody>
{#each Object.keys(server.variables) as variable, index}
<tr>
<td class="text-center">
<div class="flex justify-center items-center">
<p class="text-lg">{variable}</p>
</div>
</td>
<td>
<label>
<span>Default</span>
<input
class="input"
placeholder="default"
type="text"
bind:value={server.variables[variable].default}
/>
</label>
</td>
<td>
<label>
<span>Description</span>
<textarea
class="textarea"
placeholder="description"
bind:value={server.variables[variable].description}
/>
</label>
</td>
<td class="!w-1/3">
<div>
<label for="Enum">
<span>Enum</span>
<InputChip
label="Enum"
bind:value={server.variables[variable].enum}
name="enum"
placeholder="enum (optional) - press enter to add more items"
{#if Object.keys(server.variables).length === 0}
<p class="text-sm">No variables found in the URL.</p>
{:else}
<table class="table my-2">
<tbody>
{#each Object.keys(server.variables) as variable, index}
<tr>
<td class="text-center">
<div class="flex justify-center items-center">
<p class="text-lg">{variable}</p>
</div>
</td>
<td>
<label>
<span>Default</span>
<input
class="input"
placeholder="default"
type="text"
bind:value={server.variables[variable].default}
/>
</label>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</td>
<td>
<label>
<span>Description</span>
<textarea
class="textarea"
placeholder="description"
bind:value={server.variables[variable].description}
/>
</label>
</td>
<td class="!w-1/3">
<div>
<label for="Enum">
<span>Enum</span>
<InputChip
label="Enum"
bind:value={server.variables[variable].enum}
name="enum"
placeholder="enum (optional) - press enter to add more items"
/>
</label>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
{/if}
</svelte:fragment>
</AccordionItem>
</Accordion>

View File

@@ -0,0 +1,116 @@
<script lang="ts">
import type { OpenAPIV3_1 } from 'openapi-types';
import ServerInput from '../ServerInput.svelte';
import ParameterInput from '../ParameterInput.svelte';
import { version } from '$app/environment';
export let path: OpenAPIV3_1.PathItemObject;
export const editedPath: OpenAPIV3_1.PathItemObject = {
summary: path.summary ?? '',
description: path.description ?? '',
servers: path.servers ?? [],
parameters: path.parameters ?? [],
get: path.get ?? undefined,
put: path.put ?? undefined,
post: path.post ?? undefined,
delete: path.delete ?? undefined,
options: path.options ?? undefined,
head: path.head ?? undefined,
patch: path.patch ?? undefined,
trace: path.trace ?? undefined
};
const addServer = () => {
if (!editedPath.servers) editedPath.servers = [];
editedPath.servers.push({
url: '',
description: '',
variables: {}
});
editedPath.servers = editedPath.servers;
};
const removeServer = (index: number) => {
if (!editedPath.servers) return;
editedPath.servers.splice(index, 1);
editedPath.servers = editedPath.servers;
};
const removeParameter = (index: number) => {
if (!editedPath.parameters) return;
editedPath.parameters.splice(index, 1);
editedPath.parameters = editedPath.parameters;
};
</script>
<div class="flex flex-col gap-1">
<p class="font-bold h5">Route Information</p>
<label>
<p class="font-bold">Summary</p>
<input
class="input"
type="text"
bind:value={editedPath.summary}
placeholder="A short summary of the route"
/>
</label>
<label>
<p class="font-bold">Description</p>
<textarea
class="textarea"
bind:value={editedPath.description}
placeholder="A longer description of the route (supports markdown)"
/>
</label>
</div>
<hr />
<div class="flex flex-col gap-2">
<p class="font-bold h5">Servers</p>
{#if editedPath.servers}
{#each editedPath.servers as server, i}
<div class="ring-1 ring-surface-600-300-token rounded-container-token p-2 space-y-2">
<ServerInput id={i} {server} />
<button
type="button"
class="btn btn-sm variant-filled-error"
on:click={() => {
removeServer(i);
}}
>
Remove Server
</button>
</div>
{/each}
{/if}
<span>
<button type="button" class="btn btn-sm variant-filled-primary" on:click={addServer}>
Add Server
</button>
</span>
</div>
<hr />
<div class="flex flex-col gap-2">
<p class="font-bold h5">Parameters</p>
{#if editedPath.parameters}
{#each editedPath.parameters as parameter, i}
<div class="ring-1 ring-surface-600-300-token rounded-container-token p-2 space-y-2">
<!-- If the parameter is a reference: show the reference link -->
{#if '$ref' in parameter}
<p class="text-sm">This parameter is a reference</p>
{:else}
<ParameterInput parameter={parameter} />
{/if}
<button
type="button"
class="btn btn-sm variant-filled-error"
on:click={() => {
removeParameter(i);
}}
>
Remove Parameter
</button>
</div>
{/each}
{/if}
</div>

View File

@@ -3,3 +3,4 @@
</script>
<Paths />

View File

@@ -1,163 +1,77 @@
<script lang="ts">
import { HttpMethods, openApiStore } from '$lib';
import ParameterInput from '$lib/components/atoms/ParameterInput.svelte';
import { getPathVariables } from '$lib/pathHandling';
import type { OpenAPIV3 } from '$lib/openAPITypes';
import PathEditing from '$lib/components/atoms/paths/PathEditing.svelte';
import type { OpenAPIV3_1 } from 'openapi-types';
import type { PageData } from './$types';
import { Accordion, AccordionItem } from '@skeletonlabs/skeleton';
import { getPathVariables } from '$lib';
export let data: PageData;
let initialPath: OpenAPIV3_1.PathItemObject = (data.path as OpenAPIV3_1.PathItemObject) ?? {};
let editedPath: OpenAPIV3_1.PathItemObject;
let newParam: 'query' | 'header' | 'cookie' = 'query';
let tempPath: OpenAPIV3.PathItemObject = {
parameters: []
};
openApiStore.subscribe((store) => {
if (!data.pathName) return;
if (store.paths == undefined) tempPath = {};
if (!store.paths!.hasOwnProperty(data.pathName)) tempPath = {};
// @ts-expect-error - working with a known not empty object
tempPath = store.paths[data.pathName] ?? {};
if (!tempPath.hasOwnProperty('parameters')) tempPath.parameters = [];
});
// if initialPath has variables, that are not yet included, add them
const pathVariables = getPathVariables(data.pathName ?? '');
pathVariables.forEach((variable) => {
// push path variables to the parameters array
// @ts-expect-error - working with a array thats loosely defined
tempPath.parameters.push({
// if a parameter with the same name does not exist, add it
if (
initialPath.parameters?.some((param) => {
// if the parameter is a reference skip it
if ('$ref' in param) return false;
return param.name === variable;
})
) {
return;
}
if (!initialPath.parameters) initialPath.parameters = [];
initialPath.parameters.push({
name: variable,
in: 'path',
required: true
description: '',
required: false,
deprecated: false,
allowEmptyValue: false, // this SHOULD not be used, but it is a valid value
allowReserved: false, // This property only applies to parameters with an in value of query.
style: 'simple',
explode: false, // Defaults to true when style is form, otherwise false.
schema: {},
content: {},
example: {}, // any -> Example and Examples are mutually exclusive
examples: {},
});
});
</script>
<a
href="/paths"
class="btn variant-ringed-secondary hover:variant-glass-secondary my-2 gap-2 text-lg"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
</svg>
Back
</a>
<div
class="border-token border-surface-500 space-y-4 px-6 py-4 rounded-container-token variant-glass-surface"
class="border-token border-surface-500 space-y-2 px-4 py-2 rounded-container-token variant-glass-surface"
>
<h3 class="h3">
{data.pathName}
</h3>
<hr />
<Accordion>
<AccordionItem>
<svelte:fragment slot="summary">
<h4 class="h4">General</h4>
</svelte:fragment>
<svelte:fragment slot="content">
<label class="space-y-2">
<p>Summary</p>
<input
type="text"
class="input"
bind:value={tempPath.summary}
placeholder="Summary of the path"
/>
</label>
<label class="space-y-2">
<p>Description</p>
<textarea
class="textarea"
bind:value={tempPath.description}
placeholder="Description of the path. Supports markdown."
/>
</label>
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary">
<h4 class="h4">Custom Servers</h4>
</svelte:fragment>
<svelte:fragment slot="content">
<p>Here you can add custom servers for this specific call.</p>
{#if tempPath.servers && tempPath.servers.length > 0}
{#each tempPath.servers as server, index}
<label class="space-y-2">
<p>Server {index + 1}</p>
<input
type="text"
class="input"
bind:value={server.url}
placeholder="URL of the server"
/>
</label>
{/each}
{/if}
<button type="button" class="btn variant-filled-primary"> Add Server </button>
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary">
<h4 class="h4">Parameters</h4>
</svelte:fragment>
<svelte:fragment slot="content">
{#each tempPath.parameters as param}
<ParameterInput variableName={param.name} bind:value={param} location="path" />
{/each}
<span class="flex items-center gap-2">
<select name="newParameter" bind:value={newParam} class="select w-min">
<option value="query">Query</option>
<option value="header">Header</option>
<option value="cookie">Cookie</option>
</select>
<button type="button" class="btn variant-filled-primary">
Add {newParam} Parameter
</button>
</span>
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary">
<h4 class="h4">Operations</h4>
</svelte:fragment>
<svelte:fragment slot="content">
<p>
Here you can add operations for this path. Select only the operations you want to support
</p>
<div class="flex gap-4">
{#each Object.values(HttpMethods) as method}
<label class="flex items-center gap-2">
<input
type="checkbox"
class="checkbox"
on:input={(event) => {
if (event.target.checked) {
tempPath[method] = {
tags: [],
summary: '',
description: '',
externalDocs: {
description: '',
url: ''
},
operationId: '',
parameters: [],
requestBody: {
content: {},
description: '',
required: false
},
responses: {},
callbacks: {},
deprecated: false,
security: [],
servers: []
};
}
}}
/>
{method.toUpperCase()}
</label>
{/each}
</div>
</svelte:fragment>
</AccordionItem>
</Accordion>
<PathEditing path={initialPath} bind:editedPath />
<hr />
original:
<pre>
{JSON.stringify(data, null, 2)}
</pre>
edited:
<pre>
{JSON.stringify(editedPath, null, 2)}
</pre>
</div>