Skip to content

Commit

Permalink
feat(open-api): improve rendering of (response) schemas
Browse files Browse the repository at this point in the history
INT-407
  • Loading branch information
FreekVR committed Apr 8, 2024
1 parent e3f4348 commit a5d2040
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 163 deletions.
65 changes: 39 additions & 26 deletions src/.vuepress/theme/client/components/global/OpenApiResponses.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,45 @@
<p v-if="response.description">{{ response.description }}</p>

<template v-for="(item, type) in response.content">
<OpenApiSchema
v-if="'schema' in item"
:key="`schema${type}`"
:schema="item.schema" />
<CodeGroup
v-else-if="'example' in item && item.example"
:key="`example${type}`"
:items="[
{
title: type,
code: JSON.stringify(item.example),
language: 'json',
},
]">
</CodeGroup>
<CodeGroup
v-else-if="'examples' in item && Object.entries(item.examples)?.length"
:key="`examples${type}`"
:items="
Object.entries(item.examples).map(([key, value]) => ({
title: key,
code: JSON.stringify(value),
language: 'json',
}))
">
</CodeGroup>
<section
v-if="'schema' in item && !!item.schema"
:key="`schema${type}`">
<h5>Schema</h5>
<OpenApiSchema
:title="type.toString()"
:schema="item.schema" />
</section>

<section
v-if="'example' in item && item.example"
:key="`example${type}`">
<h5>Example</h5>
<CodeGroup
:items="[
{
title: type.toString(),
code: JSON.stringify(item.example),
language: 'json',
},
]">
</CodeGroup>
</section>

<section
v-if="'examples' in item && item.examples"
:key="`examples${type}`">
<h5>Examples</h5>
<CodeGroup
v-if="'examples' in item && Object.entries(item.examples)?.length"
:items="
Object.entries(item.examples).map(([key, value]) => ({
title: key,
code: JSON.stringify(value),
language: 'json',
}))
">
</CodeGroup>
</section>
</template>
</li>
</ul>
Expand Down
237 changes: 100 additions & 137 deletions src/.vuepress/theme/client/components/global/OpenApiSchema.vue
Original file line number Diff line number Diff line change
@@ -1,144 +1,78 @@
<template>
<div
v-if="schema"
class="">
<strong
v-if="title"
class="items-center schema-row">
{{ title }}
</strong>
<strong v-else-if="schema.title">
{{ schema.title }}
</strong>
<p v-if="schema.description">
{{ schema.description }}
</p>
<div
v-if="schema.type"
class="items-center schema-row">
<span class="font-bold mr-2 schema-row-label">Type:</span>
<span class="schema-row-value">{{ schema.type }}</span>
</div>
<div
v-if="schema.format"
class="items-center schema-row">
<span class="font-bold mr-2 schema-row-label">Format:</span>
<span class="schema-row-value">{{ schema.format }}</span>
</div>
<div
v-if="schema.enum"
class="items-center schema-row">
<div class="font-bold mr-2 schema-row-label">Enum:</div>
<div class="p-2 schema-row-value">
<ul class="list-disc">
<li
v-for="(value, index) in schema.enum"
:key="index">
{{ value }}
</li>
</ul>
</div>
</div>
<div
v-if="schema.default"
class="items-center schema-row">
<span class="font-bold mr-2 schema-row-label">Default:</span>
<span class="schema-row-value">{{ schema.default }}</span>
</div>
<div
v-if="schema.pattern"
class="items-center schema-row">
<span class="font-bold mr-2 schema-row-label">Pattern:</span>
<span class="schema-row-value">{{ schema.pattern }}</span>
</div>
<template v-if="schema">
<OpenApiSchemaInfo
:schema="schema"
:title="title" />

<div
v-if="schema.items"
v-if="isArraySchemaObject(schema) && schema.items"
class="items-center schema-row">
<div class="font-bold mr-2 schema-row-label">Items:</div>
<div class="font-bold mr-2 schema-row-label">Array items:</div>
<div class="schema-row-value">
<OpenApiSchema
:schema="schema.items"
title="Items" />
</div>
</div>
<div
v-if="schema.properties"
class="items-center schema-row">
<div class="font-bold mr-2 schema-row-label">Properties:</div>
<div class="schema-row-value">
<table
:open="open"
class="border-collapse border-spacing-0">
<thead class="bg-gray-100">
<th>Name</th>
<th>Type</th>
<th>Description</th>
</thead>
<tbody>
<tr
v-for="(property, key) in schema.properties"
:key="key"
:has-nested-table="hasNesting(key, property)">
<td class="align-top">{{ key }}</td>
<td class="align-top">
<pre class="m-0 p-0">{{ property.type }}</pre>
</td>
<td class="align-top">
<strong
v-if="property.title"
class="schema-row">
{{ property.title }}
</strong>
<p
v-if="property.description"
class="schema-row">
{{ property.description }}
</p>

<div v-if="key === 'reference'">
<OpenApiSchema
:schema="property"
title="reference" />
</div>
<div
v-if="property.items"
class="px-2 py-2">
<OpenApiSchema
:schema="property.items"
title="Items" />
</div>
<div
v-if="property.properties"
class="px-2 py-2">
<OpenApiSchema :schema="{properties: property.properties}" />
</div>
<div
v-if="property.allOf"
class="px-2 py-2">
<div
v-for="(allOf, index) in property.allOf"
:key="index">
<OpenApiSchema :schema="allOf" />
</div>
</div>
<table
v-if="isSchemaObject(schema) && schema.properties"
:open="open"
class="bg-gray-50 border-collapse border-spacing-0 table-auto">
<thead class="bg-gray-200">
<th>Name</th>
<th v-if="anyPropertyHasNesting(schema.properties)">Content</th>
</thead>
<tbody>
<tr
v-for="(property, key) in schema.properties"
:key="key"
:has-nested-table="propertyHasNesting(key, Object.keys(property))">
<td class="align-top">
<OpenApiSchemaInfo
:schema="property"
:title="key.toString()" />
</td>

<td
v-if="isSchemaObject(property) && propertyHasNesting(key, Object.keys(property))"
class="align-top">
<div
v-if="isArraySchemaObject(property) && property.items"
class="px-2 py-2">
<OpenApiSchema :schema="property.items" />
</div>
<div
v-if="property.properties"
class="px-2 py-2">
<OpenApiSchema :schema="{properties: property.properties}" />
</div>
<div
v-if="property.allOf"
class="px-2 py-2">
<div
v-for="(allOf, index) in property.allOf"
:key="index">
<OpenApiSchema :schema="allOf" />
</div>
</div>
<div
v-if="property.oneOf"
class="items-center schema-row">
<div class="font-bold mr-2 schema-row-label">One of:</div>
<div class="schema-row-value">
<div
v-if="property.oneOf"
class="items-center schema-row">
<div class="font-bold mr-2 schema-row-label">One of:</div>
<div class="schema-row-value">
<div
v-for="(oneOf, index) in property.oneOf"
:key="index">
<OpenApiSchema :schema="oneOf" />
</div>
</div>
v-for="(oneOf, index) in property.oneOf"
:key="index">
<OpenApiSchema :schema="oneOf" />
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>

<div
v-if="schema.additionalProperties"
class="items-center schema-row">
Expand Down Expand Up @@ -172,25 +106,54 @@
</div>
</div>
</div>
</div>
</template>
</template>

<script setup lang="ts">
import {type OpenAPIV3_1 as OpenApiType} from 'openapi-types';
import OpenApiSchemaInfo from './OpenApiSchemaInfo.vue';
defineProps<{
schema: object;
schema: OpenApiType.SchemaObject | OpenApiType.ReferenceObject;
title?: string;
open?: boolean;
}>();
const assignAllOf = (allOf) => {
// Guard to check if the schema is an OpenApiType.SchemaObject.
const isSchemaObject = (schema: object): schema is OpenApiType.SchemaObject => {
return typeof schema === 'object';
};
// Guard to check if the schema is an OpenApiType.ArraySchemaObject.
const isArraySchemaObject = (schema: object): schema is OpenApiType.ArraySchemaObject => {
return typeof schema === 'object' && 'items' in schema;
};
const assignAllOf = (allOf: (OpenApiType.ReferenceObject | OpenApiType.SchemaObject)[]) => {
return {
properties: allOf.reduce((acc, curr) => {
return {...acc, ...curr.properties};
}, {}),
properties: allOf.reduce(
(acc: OpenApiType.SchemaObject, curr: OpenApiType.SchemaObject | OpenApiType.ReferenceObject) => {
if (isSchemaObject(curr)) {
return {...acc, ...curr.properties};
}
return {...acc};
},
{},
),
};
};
function hasNesting(key: string, property) {
return key === 'reference' || !!property.properties || !!property.items || !!property.oneOf || !!property.allOf;
function anyPropertyHasNesting(properties: OpenApiType.SchemaObject['properties']) {
return properties
? Object.keys(properties).some((key) => propertyHasNesting(key, Object.keys(properties[key])))
: false;
}
function propertyHasNesting(key: string | number, children: string[]) {
return (
key === 'reference' ||
['properties', 'items', 'oneOf', 'allOf'].some((propertyName) => children.includes(propertyName))
);
}
</script>
56 changes: 56 additions & 0 deletions src/.vuepress/theme/client/components/global/OpenApiSchemaInfo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<template>
<header>
<strong v-if="title">{{ title }}</strong>
<strong v-else-if="schema && schema.title">{{ schema.title }}</strong>
<pre
v-if="schema && schema.type"
class="m-0 p-0 text-sm whitespace-nowrap">
{{ schema.type }}
<template v-if="schema.format">({{ schema.format }})</template>
</pre>

<p
v-if="schema && schema.description"
class="m-0 p-0 text-gray-500 text-sm">
{{ schema.description }}
</p>

<div
v-if="schema && schema.enum"
class="items-center schema-row">
<div class="font-bold mr-2 schema-row-label">Enum:</div>
<div class="p-2 schema-row-value">
<ul class="list-disc">
<li
v-for="(value, index) in schema.enum"
:key="index">
{{ value }}
</li>
</ul>
</div>
</div>

<div
v-if="schema && schema.default"
class="items-center schema-row">
<span class="font-bold mr-2 schema-row-label">Default:</span>
<span class="schema-row-value">{{ schema.default }}</span>
</div>

<div
v-if="schema && schema.pattern"
class="items-center schema-row">
<span class="font-bold mr-2 schema-row-label">Pattern:</span>
<span class="schema-row-value">{{ schema.pattern }}</span>
</div>
</header>
</template>

<script setup lang="ts">
import {type OpenAPIV3_1 as OpenApiType} from 'openapi-types';
defineProps<{
title?: string;
schema: OpenApiType.SchemaObject;
}>();
</script>

0 comments on commit a5d2040

Please sign in to comment.