mirror of
https://github.com/LukeHagar/OpenAPI.gg.git
synced 2025-12-06 04:20:29 +00:00
Rework path handling
This commit is contained in:
@@ -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">{{variableName}}</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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
116
src/lib/components/atoms/paths/PathEditing.svelte
Normal file
116
src/lib/components/atoms/paths/PathEditing.svelte
Normal 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>
|
||||
@@ -3,3 +3,4 @@
|
||||
</script>
|
||||
|
||||
<Paths />
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user