diff --git a/docs/generated/endpoints.ts b/docs/generated/endpoints.ts new file mode 100644 index 000000000..cada0854e --- /dev/null +++ b/docs/generated/endpoints.ts @@ -0,0 +1,476 @@ +/** + * Generated by orval v6.11.0 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import * as axios from 'axios'; +import type { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'; +import { useQuery, useMutation } from '@tanstack/react-query'; +import type { + UseQueryOptions, + UseMutationOptions, + QueryFunction, + MutationFunction, + UseQueryResult, + QueryKey, +} from '@tanstack/react-query'; +import { rest } from 'msw'; +import { faker } from '@faker-js/faker'; +export type CreatePetsBody = { + name: string; + tag: string; +}; + +export type ListPetsParams = { limit?: string }; + +export interface Error { + code: number; + message: string; +} + +export type Pets = Pet[]; + +export type CatType = typeof CatType[keyof typeof CatType]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const CatType = { + cat: 'cat', +} as const; + +export interface Cat { + petsRequested?: number; + type: CatType; +} + +export type DachshundBreed = typeof DachshundBreed[keyof typeof DachshundBreed]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const DachshundBreed = { + Dachshund: 'Dachshund', +} as const; + +export interface Dachshund { + length: number; + breed: DachshundBreed; +} + +export type LabradoodleBreed = + typeof LabradoodleBreed[keyof typeof LabradoodleBreed]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const LabradoodleBreed = { + Labradoodle: 'Labradoodle', +} as const; + +export interface Labradoodle { + cuteness: number; + breed: LabradoodleBreed; +} + +export type DogType = typeof DogType[keyof typeof DogType]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const DogType = { + dog: 'dog', +} as const; + +export type Dog = + | (Labradoodle & { + barksPerMinute?: number; + type: DogType; + }) + | (Dachshund & { + barksPerMinute?: number; + type: DogType; + }); + +export type PetCountry = typeof PetCountry[keyof typeof PetCountry]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const PetCountry = { + "People's_Republic_of_China": "People's Republic of China", + Uruguay: 'Uruguay', +} as const; + +export type PetCallingCode = typeof PetCallingCode[keyof typeof PetCallingCode]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const PetCallingCode = { + '+33': '+33', + '+420': '+420', +} as const; + +export type Pet = + | (Dog & { + '@id'?: string; + id: number; + name: string; + tag?: string; + email?: string; + callingCode?: PetCallingCode; + country?: PetCountry; + }) + | (Cat & { + '@id'?: string; + id: number; + name: string; + tag?: string; + email?: string; + callingCode?: PetCallingCode; + country?: PetCountry; + }); + +type AwaitedInput = PromiseLike | T; + +type Awaited = O extends AwaitedInput ? T : never; + +/** + * @summary List all pets + */ +export const listPets = ( + params?: ListPetsParams, + options?: AxiosRequestConfig, +): Promise> => { + return axios.default.get(`/pets`, { + ...options, + params: { ...params, ...options?.params }, + }); +}; + +export const getListPetsQueryKey = (params?: ListPetsParams) => [ + `/pets`, + ...(params ? [params] : []), +]; + +export type ListPetsQueryResult = NonNullable< + Awaited> +>; +export type ListPetsQueryError = AxiosError; + +export const useListPets = < + TData = Awaited>, + TError = AxiosError, +>( + params?: ListPetsParams, + options?: { + query?: UseQueryOptions< + Awaited>, + TError, + TData + >; + axios?: AxiosRequestConfig; + }, +): UseQueryResult & { queryKey: QueryKey } => { + const { query: queryOptions, axios: axiosOptions } = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getListPetsQueryKey(params); + + const queryFn: QueryFunction>> = ({ + signal, + }) => listPets(params, { signal, ...axiosOptions }); + + const query = useQuery>, TError, TData>( + queryKey, + queryFn, + queryOptions, + ) as UseQueryResult & { queryKey: QueryKey }; + + query.queryKey = queryKey; + + return query; +}; + +/** + * @summary Create a pet + */ +export const createPets = ( + createPetsBody: CreatePetsBody, + options?: AxiosRequestConfig, +): Promise> => { + return axios.default.post(`/pets`, createPetsBody, options); +}; + +export type CreatePetsMutationResult = NonNullable< + Awaited> +>; +export type CreatePetsMutationBody = CreatePetsBody; +export type CreatePetsMutationError = AxiosError; + +export const useCreatePets = < + TError = AxiosError, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: CreatePetsBody }, + TContext + >; + axios?: AxiosRequestConfig; +}) => { + const { mutation: mutationOptions, axios: axiosOptions } = options ?? {}; + + const mutationFn: MutationFunction< + Awaited>, + { data: CreatePetsBody } + > = (props) => { + const { data } = props ?? {}; + + return createPets(data, axiosOptions); + }; + + return useMutation< + Awaited>, + TError, + { data: CreatePetsBody }, + TContext + >(mutationFn, mutationOptions); +}; + +/** + * @summary Info for a specific pet + */ +export const showPetById = ( + petId: string, + options?: AxiosRequestConfig, +): Promise> => { + return axios.default.get(`/pets/${petId}`, options); +}; + +export const getShowPetByIdQueryKey = (petId: string) => [`/pets/${petId}`]; + +export type ShowPetByIdQueryResult = NonNullable< + Awaited> +>; +export type ShowPetByIdQueryError = AxiosError; + +export const useShowPetById = < + TData = Awaited>, + TError = AxiosError, +>( + petId: string, + options?: { + query?: UseQueryOptions< + Awaited>, + TError, + TData + >; + axios?: AxiosRequestConfig; + }, +): UseQueryResult & { queryKey: QueryKey } => { + const { query: queryOptions, axios: axiosOptions } = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getShowPetByIdQueryKey(petId); + + const queryFn: QueryFunction>> = ({ + signal, + }) => showPetById(petId, { signal, ...axiosOptions }); + + const query = useQuery< + Awaited>, + TError, + TData + >(queryKey, queryFn, { enabled: !!petId, ...queryOptions }) as UseQueryResult< + TData, + TError + > & { queryKey: QueryKey }; + + query.queryKey = queryKey; + + return query; +}; + +export const getListPetsMock = () => + Array.from( + { length: faker.datatype.number({ min: 1, max: 10 }) }, + (_, i) => i + 1, + ).map(() => + faker.helpers.arrayElement([ + { + cuteness: faker.datatype.number({ min: undefined, max: undefined }), + breed: faker.helpers.arrayElement(['Labradoodle']), + barksPerMinute: faker.helpers.arrayElement([ + faker.datatype.number({ min: undefined, max: undefined }), + undefined, + ]), + type: faker.helpers.arrayElement(['dog']), + }, + { + length: faker.datatype.number({ min: undefined, max: undefined }), + breed: faker.helpers.arrayElement(['Dachshund']), + barksPerMinute: faker.helpers.arrayElement([ + faker.datatype.number({ min: undefined, max: undefined }), + undefined, + ]), + type: faker.helpers.arrayElement(['dog']), + '@id': faker.helpers.arrayElement([faker.random.word(), undefined]), + id: faker.datatype.number({ min: undefined, max: undefined }), + name: faker.random.word(), + tag: faker.helpers.arrayElement([faker.random.word(), undefined]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33']), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement(["People's Republic of China", 'Uruguay']), + undefined, + ]), + }, + { + petsRequested: faker.helpers.arrayElement([ + faker.datatype.number({ min: undefined, max: undefined }), + undefined, + ]), + type: faker.helpers.arrayElement(['cat']), + '@id': faker.helpers.arrayElement([faker.random.word(), undefined]), + id: faker.datatype.number({ min: undefined, max: undefined }), + name: faker.random.word(), + tag: faker.helpers.arrayElement([faker.random.word(), undefined]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33']), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement(["People's Republic of China", 'Uruguay']), + undefined, + ]), + }, + ]), + ); + +export const getCreatePetsMock = () => + faker.helpers.arrayElement([ + { + cuteness: faker.datatype.number({ min: undefined, max: undefined }), + breed: faker.helpers.arrayElement(['Labradoodle']), + barksPerMinute: faker.helpers.arrayElement([ + faker.datatype.number({ min: undefined, max: undefined }), + undefined, + ]), + type: faker.helpers.arrayElement(['dog']), + }, + { + length: faker.datatype.number({ min: undefined, max: undefined }), + breed: faker.helpers.arrayElement(['Dachshund']), + barksPerMinute: faker.helpers.arrayElement([ + faker.datatype.number({ min: undefined, max: undefined }), + undefined, + ]), + type: faker.helpers.arrayElement(['dog']), + '@id': faker.helpers.arrayElement([faker.random.word(), undefined]), + id: faker.datatype.number({ min: undefined, max: undefined }), + name: faker.random.word(), + tag: faker.helpers.arrayElement([faker.random.word(), undefined]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33']), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement(["People's Republic of China", 'Uruguay']), + undefined, + ]), + }, + { + petsRequested: faker.helpers.arrayElement([ + faker.datatype.number({ min: undefined, max: undefined }), + undefined, + ]), + type: faker.helpers.arrayElement(['cat']), + '@id': faker.helpers.arrayElement([faker.random.word(), undefined]), + id: faker.datatype.number({ min: undefined, max: undefined }), + name: faker.random.word(), + tag: faker.helpers.arrayElement([faker.random.word(), undefined]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33']), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement(["People's Republic of China", 'Uruguay']), + undefined, + ]), + }, + ]); + +export const getShowPetByIdMock = () => + faker.helpers.arrayElement([ + { + cuteness: faker.datatype.number({ min: undefined, max: undefined }), + breed: faker.helpers.arrayElement(['Labradoodle']), + barksPerMinute: faker.helpers.arrayElement([ + faker.datatype.number({ min: undefined, max: undefined }), + undefined, + ]), + type: faker.helpers.arrayElement(['dog']), + }, + { + length: faker.datatype.number({ min: undefined, max: undefined }), + breed: faker.helpers.arrayElement(['Dachshund']), + barksPerMinute: faker.helpers.arrayElement([ + faker.datatype.number({ min: undefined, max: undefined }), + undefined, + ]), + type: faker.helpers.arrayElement(['dog']), + '@id': faker.helpers.arrayElement([faker.random.word(), undefined]), + id: faker.datatype.number({ min: undefined, max: undefined }), + name: faker.random.word(), + tag: faker.helpers.arrayElement([faker.random.word(), undefined]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33']), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement(["People's Republic of China", 'Uruguay']), + undefined, + ]), + }, + { + petsRequested: faker.helpers.arrayElement([ + faker.datatype.number({ min: undefined, max: undefined }), + undefined, + ]), + type: faker.helpers.arrayElement(['cat']), + '@id': faker.helpers.arrayElement([faker.random.word(), undefined]), + id: faker.datatype.number({ min: undefined, max: undefined }), + name: faker.random.word(), + tag: faker.helpers.arrayElement([faker.random.word(), undefined]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33']), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement(["People's Republic of China", 'Uruguay']), + undefined, + ]), + }, + ]); + +export const getSwaggerPetstoreMSW = () => [ + rest.get('*/pets', (_req, res, ctx) => { + return res( + ctx.delay(1000), + ctx.status(200, 'Mocked status'), + ctx.json(getListPetsMock()), + ); + }), + rest.post('*/pets', (_req, res, ctx) => { + return res( + ctx.delay(1000), + ctx.status(200, 'Mocked status'), + ctx.json(getCreatePetsMock()), + ); + }), + rest.get('*/pets/:petId', (_req, res, ctx) => { + return res( + ctx.delay(1000), + ctx.status(200, 'Mocked status'), + ctx.json(getShowPetByIdMock()), + ); + }), +]; diff --git a/docs/package.json b/docs/package.json index b24254da5..ea8b0c959 100644 --- a/docs/package.json +++ b/docs/package.json @@ -17,18 +17,22 @@ "@mdx-js/mdx": "^1.6.18", "@mdx-js/react": "^1.6.18", "@mdx-js/tag": "^0.20.3", + "@monaco-editor/react": "^4.4.6", "@next/mdx": "^10.0.1", "@octokit/graphql": "^5.0.4", "@reactions/component": "^2.0.2", "@sentry/browser": "^5.27.3", "@sentry/node": "^5.27.3", + "@tanstack/react-query": "^4.22.0", "@zeit/fetch": "^6.0.0", "@zeit/react-jsx-parser": "^2.0.0", "async-sema": "^3.1.0", + "axios": "^1.2.2", "body-scroll-lock": "^3.1.5", "classnames": "^2.2.6", "copy-to-clipboard": "^3.3.1", "date-fns": "^2.16.1", + "dedent": "^0.7.0", "docsearch.js": "^2.6.3", "framer-motion": "^1.11.1", "gray-matter": "^4.0.2", @@ -40,11 +44,14 @@ "next-images": "^1.5.0", "next-optimized-images": "^2.6.2", "node-fetch": "^2.6.1", + "orval": "^6.11.0", + "prettier": "^2.8.3", "prismjs": "^1.21.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-icons": "^3.11.0", "react-live": "^2.2.2", + "react-select": "^5.7.0", "rehype-format": "^3.0.1", "rehype-stringify": "^7.0.0", "remark": "^12.0.1", @@ -58,7 +65,8 @@ "remark-unwrap-images": "^2.0.0", "scroll-into-view-if-needed": "^2.2.26", "semver-regex": "^3.1.1", - "unist-util-visit": "^2.0.3" + "unist-util-visit": "^2.0.3", + "yaml": "^2.2.1" }, "devDependencies": { "@babel/cli": "^7.11.6", diff --git a/docs/src/components/Nav.js b/docs/src/components/Nav.js index 717fc99b8..5418b344f 100644 --- a/docs/src/components/Nav.js +++ b/docs/src/components/Nav.js @@ -1,5 +1,4 @@ import Link from 'next/link'; -import * as React from 'react'; import { siteConfig } from 'siteConfig'; import logoSrc from '../images/orval-logo-horizontal.svg'; import { Search } from './Search'; @@ -28,6 +27,11 @@ export const Nav = () => ( Docs +
+ + Playground + +
{ + const [index, setIndex] = useState(0); + const editorContent = error || outputArray?.[index].content || ''; + + useEffect(() => { + setIndex(0); + }, [outputArray]); + + return ( + <> +
+
+ {outputArray?.map((outputItem, i) => ( +
setIndex(i)} key={outputItem.filename}> + {basename(outputItem.filename)} +
+ ))} +
+
+ + + ); +}; diff --git a/docs/src/components/playground/Editor.js b/docs/src/components/playground/Editor.js new file mode 100644 index 000000000..fcb20fa1b --- /dev/null +++ b/docs/src/components/playground/Editor.js @@ -0,0 +1,25 @@ +import MonacoEditor from '@monaco-editor/react'; + +const canUseDOM = typeof window !== 'undefined'; + +export const Editor = ({ value, lang, readOnly, onEdit, height = 500 }) => { + if (!canUseDOM) { + return null; + } + + return ( + + ); +}; diff --git a/docs/src/components/playground/Examples.js b/docs/src/components/playground/Examples.js new file mode 100644 index 000000000..20618e054 --- /dev/null +++ b/docs/src/components/playground/Examples.js @@ -0,0 +1,216 @@ +import dedent from 'dedent'; + +const SCHEMA = dedent(/* YAML */ ` + openapi: '3.0.0' + info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT + servers: + - url: http://petstore.swagger.io/v1 + paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: string + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Pets' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + summary: Create a pet + operationId: createPets + tags: + - pets + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - 'name' + - 'tag' + properties: + name: + type: string + tag: + type: string + responses: + '200': + description: Created Pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + - name: testId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + components: + schemas: + Pet: + type: object + required: + - id + - name + oneOf: + - $ref: '#/components/schemas/Dog' + - $ref: '#/components/schemas/Cat' + properties: + '@id': + type: string + format: iri-reference + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + email: + type: string + format: email + callingCode: + type: string + enum: ['+33', '+420', '+33'] # intentional duplicated value + country: + type: string + enum: ["People's Republic of China", 'Uruguay'] + Dog: + type: object + oneOf: + - $ref: '#/components/schemas/Labradoodle' + - $ref: '#/components/schemas/Dachshund' + required: ['type'] + properties: + barksPerMinute: + type: integer + type: + type: string + enum: + - dog + discriminator: + propertyName: breed + mapping: + Labradoodle: '#/components/schemas/Labradoodle' + Dachshund: '#/components/schemas/Dachshund' + Labradoodle: + type: object + required: ['cuteness'] + properties: + cuteness: + type: integer + Dachshund: + type: object + required: ['length'] + properties: + length: + type: integer + Cat: + type: object + required: ['type'] + properties: + petsRequested: + type: integer + type: + type: string + enum: + - cat + Pets: + type: array + items: + $ref: '#/components/schemas/Pet' + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + +`); + +export const EXAMPLES = { + ReactQuery: [ + { + name: 'Basic', + description: `This is the simplest example of generating output based on a specification.`, + tags: [], + config: dedent(/* JSON */ `{ + "output": { + "client": "react-query", + "target": "./src/generated/endpoints.ts", + "mock": true + }, + "input": { + "target": "./schema.yaml" + } + } + `), + schema: SCHEMA, + }, + ], +}; diff --git a/docs/src/components/playground/Playground.js b/docs/src/components/playground/Playground.js new file mode 100644 index 000000000..da2b34a97 --- /dev/null +++ b/docs/src/components/playground/Playground.js @@ -0,0 +1,107 @@ +//import { Image, useTheme } from '@theguild/components'; +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; +import { useState } from 'react'; +import Select from 'react-select'; +import { EXAMPLES } from './Examples'; +import { PlaygroundEditors } from './PlaygroundEditors'; + +const groupedExamples = Object.entries(EXAMPLES).map(([catName, category]) => ({ + label: catName, + options: category.map((t, index) => ({ + ...t, + selectId: `${catName}__${index}`, + })), +})); + +const DEFAULT_EXAMPLE = { + catName: 'ReactQuery', + index: 0, +}; + +export function Playground({ height }) { + const [template, setTemplate] = useState( + `${DEFAULT_EXAMPLE.catName}__${DEFAULT_EXAMPLE.index}`, + ); + const [schema, setSchema] = useState( + EXAMPLES[DEFAULT_EXAMPLE.catName][DEFAULT_EXAMPLE.index].schema, + ); + const [config, setConfig] = useState( + EXAMPLES[DEFAULT_EXAMPLE.catName][DEFAULT_EXAMPLE.index].config, + ); + + const { data: output, error } = useQuery( + [config, schema, template], + async () => { + const response = await axios.post('/api/generate', { + config, + schema, + }); + + return response.data; + }, + { + retry: false, + }, + ); + + const changeTemplate = (value) => { + const [catName, index] = value.split('__'); + setSchema(EXAMPLES[catName][index].schema); + setConfig(EXAMPLES[catName][index].config); + setTemplate(value); + }; + + return ( +
+
+

Choose Live Example:

+