Skip to content

Commit

Permalink
Create PoC for generating model docs from open API JSON (#342)
Browse files Browse the repository at this point in the history
  • Loading branch information
szuperaz authored May 13, 2024
2 parents 3622409 + c906efb commit ab405cd
Show file tree
Hide file tree
Showing 17 changed files with 42,965 additions and 40 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ ts/
!.yarn/versions
docusaurus/video/docusaurus/shared
node_modules/.yarn-integrity
yarn.lock

node_modules
48 changes: 48 additions & 0 deletions docusaurus/video/docusaurus/docs/api/_common_/CallEventModels.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import OpenApiModels from './OpenApiModels';
import apiJson from '../video-client-openapi.json';

const filter = (apiJson) =>
Object.keys(apiJson.components.schemas).filter(
(key) =>
apiJson.components.schemas[key]['x-stream-event-call-type'] === true,
);

const events = filter(apiJson).map((key) => {
const type = apiJson.components.schemas[key].properties.type.default || '-';
const description = apiJson.components.schemas[key].description || '-';

return { key, type, description };
});

events.sort((e1, e2) => (e1.type < e2.type ? -1 : e1.type > e2.type ? 1 : 0));

const CallEventModels = () => {
return (
<React.Fragment>
<table>
<thead>
<th>Name</th>
<th>Description</th>
</thead>
{events.map((event) => (
<tr>
<td>
<a href={'#' + event.key}>
<code>{event.type}</code>
</a>
</td>
<td>{event.description}</td>
</tr>
))}
</table>
<OpenApiModels
modelFilter={filter}
recursive={true}
apiJson={apiJson}
></OpenApiModels>
</React.Fragment>
);
};

export default CallEventModels;
50 changes: 50 additions & 0 deletions docusaurus/video/docusaurus/docs/api/_common_/OpenApiModels.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { parseModel } from './open-api-model-parser';

const OpenApiModels = ({ modelName, modelFilter, recursive = true, apiJson }) => {

const models = React.useMemo(() => {
if (!modelName && !modelFilter) {
return [];
}
return parseModel({modelName, modelFilter, recursive, apiJson});
}, [modelName, modelFilter]);

return (
<div>
{models.map((model) => (
<React.Fragment key={model.name}>
<h2 id={model.name}>{model.name}</h2>
<table data-testid={model.name + '-table'}>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Constraints</th>
</tr>
</thead>
<tbody>
{model.properties.map(p => {
return (
<tr key={model.name + p.name}>
<td data-testid={model.name + '-' + p.name + '-name'}>
<code>{p.name}</code>
</td>
<td data-testid={model.name + '-' + p.name + '-type'}>
{p.type.definitionLink ? <a data-testid={model.name + '-' + p.name + '-typelink'} href={p.type.definitionLink}><code>{p.type.formattedName}</code></a> : <code>{p.type.formattedName}</code>}
</td>
<td data-testid={model.name + '-' + p.name + '-description'}>{p.description || '-'}</td>
<td data-testid={model.name + '-' + p.name + '-constraints'}>{p.constraints.join(', ') || '-'}</td>
</tr>
);
})}
</tbody>
</table>
</React.Fragment>
))}
</div>
);
};

export default OpenApiModels;
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import videoApiJson from '../video-openapi.json';

export const parseModel = ({ modelName, modelFilter, recursive = true, apiJson = videoApiJson }) => {
const models = modelName ? [{name: modelName, properties: []}] : modelFilter(apiJson).map(_modelName => ({name: _modelName, properties: []}));

for (let i = 0; i < models.length; i++) {
const model = models[i];

const schemaJson = apiJson.components.schemas[model.name];
const propertiesJson = schemaJson.properties;

model.properties = Object.keys(propertiesJson).map(propertyName => {
const property = propertiesJson[propertyName];

const type = parsePropertyType(property);

// enums are not yet parsed
const isEnum = apiJson.components.schemas[type.name] && !apiJson.components.schemas[type.name].properties;
const shouldDisplayTypeDefLink = recursive && !isEnum;

if (recursive && !isEnum && apiJson.components.schemas[type.name] && !models.find(r => r.name === type.name)) {
models.push({name: type.name, properties: []});
}

const description = parsePropertyDescription(property);

const isRequired = schemaJson.required?.includes(propertyName);
const constraints = parsePropertyConstraints(property, isRequired);


return {
name: propertyName,
type: {...type, definitionLink: shouldDisplayTypeDefLink ? type.definitionLink : undefined},
constraints,
description
}
});
}

return models;
}

export const parsePropertyType = (property) => {
let name;
let definitionLink;
let formattedName;
let isArray = property.type === 'array';
if (property.$ref || isArray && property.items?.$ref) {
const ref = isArray ? property.items?.$ref : property.$ref;
// Example $ref: #/components/schemas/EdgeResponse
name = ref?.split('/')?.pop() || '';
definitionLink = `#${name}`;
} else {
name = (isArray ? property.items?.type : property.type) || ''
}

formattedName = name;

if (property.enum || isArray && property.items.enum) {
const enumValues = isArray ? property.items.enum : property.enum;
formattedName += ` (${enumValues.join(', ')})`
}

formattedName = isArray ? `${formattedName}[]` : formattedName;

return {
name, definitionLink, formattedName, isArray
}
}

export const parsePropertyDescription = (property) => {
return property.description;
}

export const parsePropertyConstraints = (property, required) => {
const constraints = [];
if (required) {
constraints.push('Required');
}
if (property.minimum !== undefined) {
constraints.push(`Minimum: ${property.minimum}`)
}
if (property.maximum !== undefined) {
constraints.push(`Maximum: ${property.maximum}`)
}

return constraints;
}
5 changes: 5 additions & 0 deletions docusaurus/video/docusaurus/docs/api/basics/calls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import CallFilters from '../../../shared/video/_call-filters.mdx';
import CallMemberFilters from '../../../shared/video/_call-member-filters.mdx';
import CallSort from '../../../shared/video/_call-sort-fields.mdx';
import CallMemberSort from '../../../shared/video/_call-member-sort-fields.mdx';
import OpenApiModels from '../_common_/OpenApiModels';

## Creating calls

Expand Down Expand Up @@ -68,6 +69,10 @@ await client.call('default', 'test-outgoing-call').getOrCreate({
</TabItem>
</Tabs>

### Model

<OpenApiModels modelName={'GetOrCreateCallRequest'}></OpenApiModels>

## Updating calls

<UpdateCall />
Expand Down
Loading

0 comments on commit ab405cd

Please sign in to comment.