Skip to content

Commit

Permalink
feat: schema examples
Browse files Browse the repository at this point in the history
  • Loading branch information
enzonotario committed Jan 9, 2025
1 parent 7b79af0 commit 07f6df4
Show file tree
Hide file tree
Showing 16 changed files with 481 additions and 258 deletions.
38 changes: 38 additions & 0 deletions docs/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,24 @@
"application/json": {
"schema": {
"$ref": "#/components/schemas/Artist"
},
"examples": {
"Charly": {
"value": {
"name": "Charly García",
"description": "One of the most influential rock musicians in Argentine history.",
"image": "https://cdn.rock-legends.com/photos/charly.jpg",
"band": "Sui Generis"
}
},
"Maria": {
"value": {
"name": "Maria Gabriela Epumer",
"description": "A very important rock musician in Argentine history.",
"image": "https://cdn.rock-legends.com/photos/maria.jpg",
"band": "Viudas e Hijas de Roque Enroll"
}
}
}
}
}
Expand All @@ -114,6 +132,26 @@
"application/json": {
"schema": {
"$ref": "#/components/schemas/Artist"
},
"examples": {
"Charly": {
"value": {
"id": 1,
"name": "Charly García",
"description": "One of the most influential rock musicians in Argentine history.",
"image": "https://cdn.rock-legends.com/photos/charly.jpg",
"band": "Sui Generis"
}
},
"Maria": {
"value": {
"id": 2,
"name": "Maria Gabriela Epumer",
"description": "A very important rock musician in Argentine history.",
"image": "https://cdn.rock-legends.com/photos/maria.jpg",
"band": "Viudas e Hijas de Roque Enroll"
}
}
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/components/Path/OAPath.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const openapi = props.openapi ?? getOpenApiInstance()
const operation = openapi.getOperation(props.id)
const operationData = initOperationData(operation)
provide('operationData', operationData)
const operationPath = openapi.getOperationPath(props.id)
const operationMethod = openapi.getOperationMethod(props.id)?.toUpperCase()
Expand Down Expand Up @@ -85,7 +89,7 @@ const request = ref(
baseUrl: baseUrl.value,
parameters: operationParameters ?? [],
authorizations: securityUi.length ? Object.entries(securityUi)[0]?.schemes : {},
body: operationRequestBody?.content?.[bodyRequestContentType]?.uiContentType,
body: (operationRequestBody?.content?.[bodyRequestContentType]?.examples ?? { value: '' })[0]?.value,
variables: {},
})
: {},
Expand All @@ -98,8 +102,6 @@ function updateRequest(newRequest) {
function updateSelectedServer(server) {
selectedServer.value = server
}
provide('operationData', initOperationData(operation))
</script>
<template>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Playground/OAPlaygroundParameters.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const props = defineProps({
type: Object,
required: true,
},
schemaUiContentType: {
examples: {
type: Object,
required: false,
},
Expand Down Expand Up @@ -84,7 +84,7 @@ const operationData = inject('operationData') as OperationData
const authorizations = ref<PlaygroundSecurityScheme[] | null>(null)
const body = ref(props.schemaUiContentType)
const body = ref(Object.keys(props.examples ?? {}).length ? Object.values(props.examples ?? {})[0].value : null)
function setAuthorizations(schemes: Record<string, PlaygroundSecurityScheme>) {
if (!schemes || !Object.keys(schemes).length) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Request/OARequestBody.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const props = defineProps({
<template>
<OASchemaTabs
:schema="props.requestBody?.content?.[props.contentType]?.ui"
:schema-ui-content-type="props.requestBody?.content?.[props.contentType]?.uiContentType"
:examples="props.requestBody?.content?.[props.contentType]?.examples"
:content-type="props.contentType"
:is-dark="props.isDark"
/>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Response/OAResponse.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const contentType = ref(contentTypes[0] ?? undefined)
const schema = computed(() => props.response.content?.[contentType.value]?.ui)
const schemaJson = computed(() => props.response.content?.[contentType.value]?.uiContentType)
const examples = computed(() => props.response.content?.[contentType.value]?.examples)
const contentTypeId = `content-type-${Math.random().toString(36).substring(7)}`
</script>
Expand Down Expand Up @@ -85,7 +85,7 @@ const contentTypeId = `content-type-${Math.random().toString(36).substring(7)}`
<OASchemaTabs
v-if="schema"
:schema="schema"
:schema-ui-content-type="schemaJson"
:examples="examples"
:content-type="contentType"
:is-dark="props.isDark"
/>
Expand Down
101 changes: 34 additions & 67 deletions src/components/Schema/OASchemaTabs.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
<script setup>
import { computed, ref } from 'vue'
import { computed } from 'vue'
import { useTheme } from '../../composables/useTheme'
import { hasExample } from '../../lib/examples/hasExample'
import { getSchemaUiContentType } from '../../lib/getSchemaUiContentType'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'
import { Checkbox } from '../ui/checkbox'
import { Label } from '../ui/label'
import OASchemaUI from '../Schema/OASchemaUI.vue'
import OAMarkdown from '../Common/OAMarkdown.vue'
const props = defineProps({
schema: {
type: Object,
required: true,
},
schemaUiContentType: {
type: [Object, String, Number, Boolean, Array],
required: true,
},
contentType: {
type: String,
required: true,
examples: {
type: null,
required: false,
},
isDark: {
type: Boolean,
Expand All @@ -29,46 +22,29 @@ const props = defineProps({
const themeConfig = useTheme()
const useExample = ref(true)
const defaultValue = computed(() => {
const examplesKeys = Object.keys(props.examples ?? {})
const schemaHasExample = hasExample(props.schema)
const checkboxId = `useExample-${Math.random().toString(36).substring(7)}`
const contentTypeLabel = computed(() => {
if (props.contentType.includes('json')) {
return 'JSON'
}
if (props.contentType === 'application/xml') {
return 'XML'
if (themeConfig.schemaConfig.defaultView.value === 'schema') {
return 'schema'
}
return 'Schema'
})
const lang = computed(() => {
if (props.contentType.includes('json')) {
return 'json'
}
if (props.contentType === 'application/xml') {
return 'xml'
if (themeConfig.schemaConfig.defaultView.value === 'contentType') {
return examplesKeys?.length ? `examples.${examplesKeys[0]}` : 'schema'
}
return props.contentType
})
const hasSchemaContentType = computed(() => props.schemaUiContentType !== null)
const schemaUiContentType = computed(() => {
if (useExample.value) {
return props.schemaUiContentType
if (themeConfig.schemaConfig.defaultView.value === 'autogenerated') {
const autogeneratedExample = examplesKeys.find(key => props.examples[key]?.isAutogenerated)
return autogeneratedExample ? `examples.${autogeneratedExample}` : 'schema'
}
return getSchemaUiContentType(props.contentType, props.schema, useExample.value)
return themeConfig.schemaConfig.defaultView.value
})
</script>
<template>
<Tabs
:default-value="hasSchemaContentType ? themeConfig.schemaConfig.defaultView : 'schema'"
:default-value="defaultValue"
>
<TabsList class="relative flex flex-row justify-start">
<TabsTrigger
Expand All @@ -79,12 +55,13 @@ const schemaUiContentType = computed(() => {
{{ $t('Schema') }}
</TabsTrigger>
<TabsTrigger
v-if="hasSchemaContentType"
value="contentType"
v-for="example in Object.keys(props.examples ?? {})"
:key="example"
:value="`examples.${example}`"
variant="schemaTabs"
class="h-full"
>
{{ contentTypeLabel }}
{{ example }}
</TabsTrigger>
</TabsList>
<TabsContent value="schema">
Expand All @@ -96,32 +73,22 @@ const schemaUiContentType = computed(() => {
/>
</div>
</TabsContent>
<TabsContent value="contentType">
<div class="relative flex flex-col">
<div
v-if="schemaHasExample"
class="absolute right-14 top-1 z-10 flex flex-row items-center gap-1"
>
<Checkbox
:id="checkboxId"
:checked="useExample"
name="useExample"
aria-label="Use example"
@update:checked="useExample = $event"
/>
<Label
:for="checkboxId"
class="text-gray-800 dark:text-gray-200 text-sm"
>
{{ $t('Use example') }}
</Label>
</div>
<TabsContent
v-for="example in Object.keys(props.examples ?? {})"
:key="example"
:value="`examples.${example}`"
>
<div class="flex flex-col gap-2">
<OAMarkdown
v-if="props.examples[example]?.description"
:content="props.examples[example]?.description"
/>
<OACodeBlock
v-if="hasSchemaContentType"
:code="schemaUiContentType"
:lang="lang"
:label="contentTypeLabel"
v-if="props.examples[example]?.value"
:code="props.examples[example]?.value"
:lang="props.examples[example]?.lang ?? 'json'"
:label="(props.examples[example]?.lang ?? 'json').toUpperCase()"
:is-dark="props.isDark"
class="!my-0"
/>
Expand Down
12 changes: 6 additions & 6 deletions src/components/Try/OATryWithVariables.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,20 @@ const emits = defineEmits([
const loading = ref(false)
const schemaUiContentType = computed(() => {
return props.requestBody?.content?.[props.contentType]?.uiContentType
const schemaExamples = computed(() => {
return props.requestBody?.content?.[props.contentType]?.examples
})
const hasSchemaVariables = computed(() =>
Object.keys(schemaUiContentType.value?.variables ?? {}).length > 0,
const hasBody = computed(() =>
Boolean(props.requestBody),
)
const hasSecuritySchemes = computed(() =>
Object.keys(props.securityUi ?? {}).length > 0,
)
const hasParameters = computed(() =>
Boolean(props.parameters?.length || hasSchemaVariables.value || hasSecuritySchemes.value),
Boolean(props.parameters?.length || hasBody.value || hasSecuritySchemes.value),
)
</script>

Expand All @@ -87,7 +87,7 @@ const hasParameters = computed(() =>
:base-url="props.baseUrl"
:parameters="props.parameters ?? []"
:security-ui="props.securityUi ?? {}"
:schema-ui-content-type="schemaUiContentType"
:examples="schemaExamples"
:is-dark="props.isDark"
@update:request="($event: any) => emits('update:request', $event)"
@update:selected-server="($event: any) => emits('update:selectedServer', $event)"
Expand Down
10 changes: 5 additions & 5 deletions src/composables/useTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface PathConfig {
}

export interface RequestConfig {
defaultView: Ref<'schema' | 'contentType'>
defaultView: Ref<'schema' | 'contentType' | 'autogenerated'>
}

export interface JsonViewerConfig {
Expand Down Expand Up @@ -120,7 +120,7 @@ export interface UseThemeConfigUnref {
showBaseURL: boolean
}>
requestBody?: Partial<{
defaultView: 'schema' | 'contentType'
defaultView: 'schema' | 'contentType' | 'autogenerated'
}>
jsonViewer?: Partial<{
deep: number
Expand Down Expand Up @@ -230,7 +230,7 @@ const themeConfig: UseThemeConfig = {
showBaseURL: ref<boolean>(false),
},
requestBody: {
defaultView: ref<'schema' | 'contentType'>('contentType'),
defaultView: ref<'schema' | 'contentType' | 'autogenerated'>('contentType'),
},
jsonViewer: {
deep: ref<number>(Number.POSITIVE_INFINITY),
Expand Down Expand Up @@ -446,11 +446,11 @@ export function useTheme(initialConfig: Partial<UseThemeConfigUnref> = {}) {
return themeConfig?.theme?.highlighterTheme
}

function getSchemaDefaultView(): 'schema' | 'contentType' | undefined {
function getSchemaDefaultView(): 'schema' | 'contentType' | 'autogenerated' | undefined {
return themeConfig?.requestBody?.defaultView?.value
}

function setSchemaDefaultView(value: 'schema' | 'contentType') {
function setSchemaDefaultView(value: 'schema' | 'contentType' | 'autogenerated') {
// @ts-expect-error: This is a valid expression.
themeConfig.requestBody.defaultView.value = value
}
Expand Down
34 changes: 34 additions & 0 deletions src/lib/examples/getSchemaExample.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { OAProperty } from '../getSchemaUi'
import type { OAExampleObject } from '../../types'
import { getSchemaUiJson } from './getSchemaUiJson'
import { getSchemaUiXml } from './getSchemaUiXml'

export function getSchemaExample(contentType: string, uiProperties: OAProperty[] | OAProperty, useExample = false): Record<string, OAExampleObject> {
if (contentType.toLowerCase().match(/^(text|application)\/.*xml($|;|\\+)/)) {
return {
XML: getSchemaExampleValue(contentType, uiProperties, useExample),
}
}

return {
JSON: getSchemaExampleValue(contentType, uiProperties, useExample),
}
}

function getSchemaExampleValue(contentType: string, uiProperties: OAProperty[] | OAProperty, useExample = false): OAExampleObject {
if (contentType.toLowerCase().match(/^(text|application)\/.*xml($|;|\\+)/)) {
return {
summary: 'XML',
description: '',
value: getSchemaUiXml(uiProperties, useExample),
isAutogenerated: true,
} as OAExampleObject
}

return {
summary: 'JSON',
description: '',
value: getSchemaUiJson(uiProperties, useExample),
isAutogenerated: true,
} as OAExampleObject
}
Loading

0 comments on commit 07f6df4

Please sign in to comment.