From 4636f94419ad5cec13fa95bc36f62fab62635e1c Mon Sep 17 00:00:00 2001 From: Enzo Notario Date: Thu, 14 Nov 2024 22:52:51 -0300 Subject: [PATCH] feat(useTheme): allows to customize CodeSamples langs/generator (#116) --- .../theme/components/ExampleBlock.vue | 13 + docs/.vitepress/config.mts | 4 + docs/.vitepress/theme/index.ts | 4 + docs/customizations/code-samples.md | 44 +++ docs/customizations/operation-badges.md | 61 +-- .../parts/code-samples-example.md | 68 ++++ .../parts/operation-badges-example.md | 14 + docs/layouts/custom-slots.md | 14 +- src/components/Operation/OAOperation.vue | 3 + src/components/Path/OAPath.vue | 28 +- .../Playground/OAPlaygroundParameters.vue | 91 +---- src/components/Sample/OACodeSamples.vue | 62 +-- src/components/Try/OATryItButton.vue | 14 +- src/components/Try/OATryWithVariables.vue | 40 +- src/composables/useTheme.ts | 108 ++++++ src/index.ts | 3 + src/lib/codeSamples/buildRequest.ts | 118 ++++++ src/lib/codeSamples/generateCodeSample.ts | 20 + src/lib/codeSamples/generateCodeSampleCurl.ts | 13 + .../generateCodeSampleJavaScript.ts | 31 ++ src/lib/codeSamples/generateCodeSamplePhp.ts | 50 +++ .../codeSamples/generateCodeSamplePython.ts | 32 ++ src/lib/codeSamples/request.ts | 17 + src/lib/fetchToCurl.ts | 5 +- src/lib/generateCodeSamples.ts | 40 -- test/lib/buildRequest.test.ts | 194 ++++++++++ test/lib/generateCodeSamples.test.ts | 355 +++++++++++++++--- 27 files changed, 1177 insertions(+), 269 deletions(-) create mode 100644 dev/.vitepress/theme/components/ExampleBlock.vue create mode 100644 docs/customizations/code-samples.md create mode 100644 docs/customizations/parts/code-samples-example.md create mode 100644 docs/customizations/parts/operation-badges-example.md create mode 100644 src/lib/codeSamples/buildRequest.ts create mode 100644 src/lib/codeSamples/generateCodeSample.ts create mode 100644 src/lib/codeSamples/generateCodeSampleCurl.ts create mode 100644 src/lib/codeSamples/generateCodeSampleJavaScript.ts create mode 100644 src/lib/codeSamples/generateCodeSamplePhp.ts create mode 100644 src/lib/codeSamples/generateCodeSamplePython.ts create mode 100644 src/lib/codeSamples/request.ts delete mode 100644 src/lib/generateCodeSamples.ts create mode 100644 test/lib/buildRequest.test.ts diff --git a/dev/.vitepress/theme/components/ExampleBlock.vue b/dev/.vitepress/theme/components/ExampleBlock.vue new file mode 100644 index 00000000..2bba2070 --- /dev/null +++ b/dev/.vitepress/theme/components/ExampleBlock.vue @@ -0,0 +1,13 @@ + + + diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index b6542125..d14c0b27 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -83,6 +83,10 @@ export default defineConfigWithTheme({ text: 'Operation Badges', link: '/customizations/operation-badges', }, + { + text: 'Code Samples', + link: '/customizations/code-samples', + }, ], }, ], diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index b1647f38..fef9229e 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -2,6 +2,7 @@ import { theme, useOpenapi } from 'vitepress-openapi' import DefaultTheme from 'vitepress/theme' import spec from '../../public/openapi.json' assert {type: 'json'} import 'vitepress-openapi/dist/style.css' +import ExampleBlock from '@dev/.vitepress/theme/components/ExampleBlock.vue' export default { extends: DefaultTheme, @@ -13,5 +14,8 @@ export default { // Use the theme. theme.enhanceApp({ app, openapi }) + + // Register custom components. + app.component('ExampleBlock', ExampleBlock) }, } diff --git a/docs/customizations/code-samples.md b/docs/customizations/code-samples.md new file mode 100644 index 00000000..c16476b4 --- /dev/null +++ b/docs/customizations/code-samples.md @@ -0,0 +1,44 @@ +--- +aside: false +outline: false +--- + +# Code Samples + +Code Samples allow you to showcase API request examples in multiple programming languages. +This feature helps API consumers understand how to integrate with your API using their preferred language. + +You can customize: + +- Which languages to display (`langs`) +- Available languages for selection (`availableLangs`) +- How code is generated for each language (`generator`) + + + + + + + + + diff --git a/docs/customizations/operation-badges.md b/docs/customizations/operation-badges.md index c04d39d6..76c0d802 100644 --- a/docs/customizations/operation-badges.md +++ b/docs/customizations/operation-badges.md @@ -12,7 +12,9 @@ Each operation can have different badges that indicate its state, for example if By default, only the `deprecated` badge is shown, as appropriate. You can customize the operation badges using the `useTheme({ operation: { badges: string[] })` function. **The order in which you set the badges is the order in which they will be displayed.** -For example: + + + @@ -295,6 +297,7 @@ function hasSlot(name) { :path="codeSamples.path" :method="codeSamples.method" :base-url="codeSamples.baseUrl" + :request="codeSamples.request" :is-dark="isDark" /> diff --git a/src/components/Path/OAPath.vue b/src/components/Path/OAPath.vue index 2ceebb11..fac1ab2e 100644 --- a/src/components/Path/OAPath.vue +++ b/src/components/Path/OAPath.vue @@ -1,7 +1,9 @@ @@ -182,6 +206,8 @@ const operationCols = computed(() => themeConfig.getOperationCols()) :method="operationMethod" :base-url="baseUrl" :path="operationPath" + :request="request" + :update-request="updateRequest" /> diff --git a/src/components/Playground/OAPlaygroundParameters.vue b/src/components/Playground/OAPlaygroundParameters.vue index 8f16ff0f..7e81cd3d 100644 --- a/src/components/Playground/OAPlaygroundParameters.vue +++ b/src/components/Playground/OAPlaygroundParameters.vue @@ -2,11 +2,12 @@ import { computed, defineEmits, defineProps, ref, watch } from 'vue' import OAJSONEditor from 'vitepress-openapi/components/Common/OAJSONEditor.vue' import { propertiesTypesJsonRecursive } from 'vitepress-openapi/lib/generateSchemaJson' -import { usePlayground, useTheme } from 'vitepress-openapi' +import { OARequest, usePlayground, useTheme } from 'vitepress-openapi' import { useStorage } from '@vueuse/core' import OAPlaygroundParameterInput from 'vitepress-openapi/components/Playground/OAPlaygroundParameterInput.vue' import OAPlaygroundSecurityInput from 'vitepress-openapi/components/Playground/OAPlaygroundSecurityInput.vue' import { getExample } from 'vitepress-openapi/lib/getExample' +import { buildRequest } from 'vitepress-openapi/lib/codeSamples/buildRequest' interface SecurityScheme { type: string @@ -18,10 +19,7 @@ interface SecurityScheme { const props = defineProps({ request: { // v-model type: Object, - default: () => ({ - url: '', - headers: {}, - }), + default: () => (new OARequest()), }, operationId: { type: String, @@ -90,77 +88,6 @@ const authScheme = ref(null) const body = ref(props.schema ? propertiesTypesJsonRecursive(props.schema, true) : null) -function buildRequest() { - let requestPath = props.path - - for (const [key, value] of Object.entries(variables.value)) { - if (!pathParameters.find(parameter => parameter.name === key)) { - continue - } - - if (value === undefined || value === '') { - continue - } - - requestPath = requestPath.replace(`{${key}}`, value) - } - - const url = new URL(`${props.baseUrl}${requestPath}`) - - for (const [key, value] of Object.entries(variables.value)) { - if (!queryParameters.find(parameter => parameter.name === key)) { - continue - } - - if (value === undefined || value === '') { - continue - } - - url.searchParams.set(key, value) - } - - const headers = new Headers({}) - - for (const [key, value] of Object.entries(variables.value)) { - if (!headerParameters.find(parameter => parameter.name === key)) { - continue - } - - if (value === undefined || value === '') { - continue - } - - headers.set(key, value) - } - - if (authScheme.value) { - switch (authScheme.value.type) { - case 'http': - headers.set('Authorization', `${authScheme.value.scheme === 'basic' ? 'Basic' : 'Bearer'} ${authScheme.value.value}`) - break - case 'apiKey': - headers.set(authScheme.value.name, authScheme.value.value) - break - case 'openIdConnect': - headers.set('Authorization', `Bearer ${authScheme.value.value}`) - break - case 'oauth2': - headers.set('Authorization', `Bearer ${authScheme.value.value}`) - break - } - } - - const newRequest = { - url: url.toString(), - headers: Object.fromEntries(headers), - body: body.value, - } - - emits('update:request', newRequest) - - return newRequest -} - function setAuthScheme(scheme: SecurityScheme) { const name = selectedSchemeName.value @@ -172,7 +99,17 @@ function setAuthScheme(scheme: SecurityScheme) { } } -watch([variables, authScheme, body], buildRequest, { deep: true, immediate: true }) +watch([variables, authScheme, body], () => { + emits('update:request', buildRequest({ + baseUrl: props.baseUrl, + method: props.method, + path: props.path, + variables: variables.value, + authScheme: authScheme.value, + body: body.value, + parameters: props.parameters, + })) +}, { deep: true }) watch(selectedScheme, () => { if (!selectedScheme.value) { diff --git a/src/components/Sample/OACodeSamples.vue b/src/components/Sample/OACodeSamples.vue index f6d22f0d..3287dfd5 100644 --- a/src/components/Sample/OACodeSamples.vue +++ b/src/components/Sample/OACodeSamples.vue @@ -1,57 +1,73 @@