page.mdx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import { pageMetadata } from "@/lib/page-metadata"
  2. export const metadata = pageMetadata("docs/openapi")
  3. # OpenAPI Integration
  4. Use json-render to generate dynamic forms and UIs from [OpenAPI/Swagger](https://swagger.io/specification/) schemas.
  5. <div className="rounded-lg border border-amber-500/50 bg-amber-500/10 p-4 mb-8">
  6. <p className="text-sm text-amber-700 dark:text-amber-300">
  7. <strong>Concept:</strong> This page demonstrates how json-render can support OpenAPI schemas. The examples are illustrative and may require adaptation for production use.
  8. </p>
  9. </div>
  10. ## Why OpenAPI?
  11. OpenAPI specifications describe your API's endpoints, request bodies, and response schemas. By converting OpenAPI schemas to json-render specs, you can:
  12. - Automatically generate forms for API endpoints
  13. - Display API responses with type-aware rendering
  14. - Keep your UI in sync with your API schema
  15. - Let AI generate UIs that match your API contracts
  16. ## Example OpenAPI Schema
  17. A typical OpenAPI schema for a request body:
  18. ```json
  19. {
  20. "openapi": "3.0.0",
  21. "paths": {
  22. "/users": {
  23. "post": {
  24. "summary": "Create a new user",
  25. "operationId": "createUser",
  26. "requestBody": {
  27. "required": true,
  28. "content": {
  29. "application/json": {
  30. "schema": {
  31. "$ref": "#/components/schemas/CreateUserRequest"
  32. }
  33. }
  34. }
  35. }
  36. }
  37. }
  38. },
  39. "components": {
  40. "schemas": {
  41. "CreateUserRequest": {
  42. "type": "object",
  43. "required": ["email", "name"],
  44. "properties": {
  45. "name": {
  46. "type": "string",
  47. "description": "User's full name",
  48. "minLength": 1,
  49. "maxLength": 100
  50. },
  51. "email": {
  52. "type": "string",
  53. "format": "email",
  54. "description": "User's email address"
  55. },
  56. "age": {
  57. "type": "integer",
  58. "minimum": 0,
  59. "maximum": 150,
  60. "description": "User's age"
  61. },
  62. "role": {
  63. "type": "string",
  64. "enum": ["admin", "user", "guest"],
  65. "default": "user",
  66. "description": "User's role"
  67. },
  68. "preferences": {
  69. "type": "object",
  70. "properties": {
  71. "newsletter": {
  72. "type": "boolean",
  73. "default": false
  74. },
  75. "theme": {
  76. "type": "string",
  77. "enum": ["light", "dark", "system"]
  78. }
  79. }
  80. }
  81. }
  82. }
  83. }
  84. }
  85. }
  86. ```
  87. ## Define an OpenAPI-to-UI Catalog
  88. Create components that map to OpenAPI data types:
  89. ```typescript
  90. import { defineCatalog } from '@json-render/core';
  91. import { schema } from '@json-render/react';
  92. import { z } from 'zod';
  93. export const openapiCatalog = defineCatalog(schema, {
  94. components: {
  95. Form: {
  96. description: 'API form container',
  97. props: z.object({
  98. operationId: z.string(),
  99. endpoint: z.string(),
  100. method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
  101. title: z.string().optional(),
  102. description: z.string().optional(),
  103. }),
  104. },
  105. StringField: {
  106. description: 'String input field',
  107. props: z.object({
  108. name: z.string(),
  109. label: z.string(),
  110. description: z.string().optional(),
  111. required: z.boolean().optional(),
  112. format: z.enum(['text', 'email', 'uri', 'uuid', 'date', 'date-time', 'password']).optional(),
  113. minLength: z.number().optional(),
  114. maxLength: z.number().optional(),
  115. pattern: z.string().optional(),
  116. placeholder: z.string().optional(),
  117. defaultValue: z.string().optional(),
  118. }),
  119. },
  120. NumberField: {
  121. description: 'Number input field',
  122. props: z.object({
  123. name: z.string(),
  124. label: z.string(),
  125. description: z.string().optional(),
  126. required: z.boolean().optional(),
  127. type: z.enum(['integer', 'number']).optional(),
  128. minimum: z.number().optional(),
  129. maximum: z.number().optional(),
  130. defaultValue: z.number().optional(),
  131. }),
  132. },
  133. BooleanField: {
  134. description: 'Boolean toggle field',
  135. props: z.object({
  136. name: z.string(),
  137. label: z.string(),
  138. description: z.string().optional(),
  139. defaultValue: z.boolean().optional(),
  140. }),
  141. },
  142. EnumField: {
  143. description: 'Enum selection field',
  144. props: z.object({
  145. name: z.string(),
  146. label: z.string(),
  147. description: z.string().optional(),
  148. required: z.boolean().optional(),
  149. options: z.array(z.object({
  150. value: z.string(),
  151. label: z.string().optional(),
  152. })),
  153. defaultValue: z.string().optional(),
  154. }),
  155. },
  156. ObjectField: {
  157. description: 'Nested object group',
  158. props: z.object({
  159. name: z.string(),
  160. label: z.string(),
  161. description: z.string().optional(),
  162. collapsible: z.boolean().optional(),
  163. }),
  164. },
  165. },
  166. actions: {
  167. submit: {
  168. description: 'Submit form to API endpoint',
  169. params: z.object({ operationId: z.string() }),
  170. },
  171. reset: {
  172. description: 'Reset form to defaults',
  173. params: z.object({}),
  174. },
  175. },
  176. });
  177. ```
  178. ## Convert OpenAPI Schema to Spec
  179. Transform OpenAPI schemas into json-render specs by recursively walking the schema properties and mapping each type to the corresponding catalog component. The converter handles nested objects, enums, arrays, and all primitive types.
  180. ## Usage Example
  181. ```tsx
  182. 'use client';
  183. import { OpenAPIForm } from './openapi-form';
  184. import { operationToSpec } from './openapi-to-spec';
  185. // Your OpenAPI schema (typically loaded from your API)
  186. const createUserSchema = {
  187. type: 'object',
  188. required: ['email', 'name'],
  189. properties: {
  190. name: { type: 'string', description: "User's full name" },
  191. email: { type: 'string', format: 'email', description: "User's email" },
  192. age: { type: 'integer', minimum: 0, maximum: 150 },
  193. role: { type: 'string', enum: ['admin', 'user', 'guest'], default: 'user' },
  194. },
  195. };
  196. // Convert to spec
  197. const spec = operationToSpec(
  198. 'createUser',
  199. 'POST',
  200. '/api/users',
  201. createUserSchema,
  202. 'Create User',
  203. 'Add a new user to the system',
  204. );
  205. export function CreateUserForm() {
  206. const handleSubmit = async (data: Record<string, unknown>) => {
  207. const response = await fetch('/api/users', {
  208. method: 'POST',
  209. headers: { 'Content-Type': 'application/json' },
  210. body: JSON.stringify(data),
  211. });
  212. if (response.ok) {
  213. console.log('User created!');
  214. }
  215. };
  216. return <OpenAPIForm spec={spec} onSubmit={handleSubmit} />;
  217. }
  218. ```
  219. ## Auto-generating from OpenAPI Document
  220. Load and parse an OpenAPI document to generate forms for all operations:
  221. ```typescript
  222. import SwaggerParser from '@apidevtools/swagger-parser';
  223. import { operationToSpec } from './openapi-to-spec';
  224. export async function loadOpenAPISpecs(specUrl: string) {
  225. const api = await SwaggerParser.dereference(specUrl);
  226. const specs: Record<string, any> = {};
  227. for (const [path, methods] of Object.entries(api.paths)) {
  228. for (const [method, operation] of Object.entries(methods)) {
  229. if (!operation.requestBody?.content?.['application/json']?.schema) continue;
  230. const schema = operation.requestBody.content['application/json'].schema;
  231. const operationId = operation.operationId || `${method}_${path.replace(/\//g, '_')}`;
  232. specs[operationId] = operationToSpec(
  233. operationId,
  234. method,
  235. path,
  236. schema,
  237. operation.summary,
  238. operation.description,
  239. );
  240. }
  241. }
  242. return specs;
  243. }
  244. // Usage
  245. const specs = await loadOpenAPISpecs('https://api.example.com/openapi.json');
  246. // specs.createUser, specs.updateUser, etc.
  247. ```
  248. ## Next
  249. Learn about [streaming](/docs/streaming) for progressive UI rendering.