Skip to content

Commit

Permalink
Refactor model parsing, add unit tests for model parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
szuperaz committed Dec 13, 2023
1 parent 47e2eb0 commit 29bfae9
Show file tree
Hide file tree
Showing 9 changed files with 1,886 additions and 70 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
72 changes: 3 additions & 69 deletions docusaurus/video/docusaurus/docs/api/_common_/OpenApiModels.jsx
Original file line number Diff line number Diff line change
@@ -1,79 +1,13 @@
import React from 'react';
import apiJson from '../video-openapi.json';
import { parseModel } from './open-api-model-parser';

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

const models = React.useMemo(() => {
if (!modelName && !modelFilter) {
return [];
}
const modelsResult = modelName ? [{name: modelName, properties: []}] : modelFilter(apiJson).map(key => ({name: key, properties: []}));

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

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

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

// Parse type info
let type;
let typeHref;
let displayType;
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
type = ref?.split('/')?.pop() || '';
// enums are not yet parsed
let isEnum = apiJson.components.schemas[type] && !apiJson.components.schemas[type].properties;
if (recursive && !isEnum) {
typeHref = `#${type}`;
}
// if properties are undefined, it's an enum, we don't yet parse enums
if (recursive && !isEnum && apiJson.components.schemas[type] && !modelsResult.find(r => r.name === type)) {
modelsResult.push({name: type, properties: []});
}
} else {
type = (isArray ? property.items?.type : property.type) || ''
}
displayType = isArray ? `${type}[]` : type;
if (property.enum) {
displayType += ` (${property.enum.join(', ')})`
}

// Parse title + description
let description = property.title;
if (property.description) {
description += ` - ${property.description}`;
}

// Parse constraints
const constraints = [];
if (schemaJson.required?.includes(key)) {
constraints.push('Required');
}
if (property.minimum !== undefined) {
constraints.push(`Minimum: ${property.minimum}`)
}
if (property.maximum !== undefined) {
constraints.push(`Maximum: ${property.maximum}`)
}

return {
name: key,
type,
displayType,
typeHref,
constraints,
description
}
});
}

return modelsResult;
return parseModel({modelName, modelFilter, recursive});
}, [modelName, modelFilter]);

return (
Expand All @@ -96,7 +30,7 @@ const OpenApiModels = ({ modelName, modelFilter, recursive = true }) => {
<code>{p.name}</code>
</td>
<td>
{p.typeHref ? <a href={p.typeHref}><code>{p.displayType}</code></a> : <code>{p.displayType}</code>}
{p.type.definitionLink ? <a href={p.type.definitionLink}><code>{p.type.formattedName}</code></a> : <code>{p.type.formattedName}</code>}
</td>
<td>{p.description || '-'}</td>
<td>{p.constraints.join(', ') || '-'}</td>
Expand Down
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;
}
177 changes: 177 additions & 0 deletions docusaurus/video/openapi-to-docs/__tests__/mock-open-api.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
{
"components": {
"schemas": {
"GetOrCreateCallRequest": {
"nullable": true,
"properties": {
"data": {
"$ref": "#/components/schemas/CallRequest",
"title": "Data",
"description": "Configuration options for the call",
"x-stream-index": "003"
},
"members_limit": {
"format": "int32",
"maximum": 100,
"type": "integer",
"x-stream-index": "006"
},
"notify": {
"description": "if provided it sends a notification event to the members for this call",
"title": "Notify",
"type": "boolean",
"x-stream-index": "005"
},
"ring": {
"description": "if provided it sends a ring event to the members for this call",
"title": "Ring",
"type": "boolean",
"x-stream-index": "004"
}
},
"type": "object",
"required": ["data"]
},
"CallRequest": {
"properties": {
"created_by": {
"$ref": "#/components/schemas/UserRequest",
"title": "The user that create this call",
"x-backend-only": true,
"x-stream-index": "008"
},
"created_by_id": {
"title": "The id of the user that create this call",
"type": "string",
"x-backend-only": true,
"x-stream-index": "009"
},
"custom": {
"additionalProperties": {},
"type": "object",
"x-stream-index": "010"
},
"members": {
"items": {
"$ref": "#/components/schemas/MemberRequest"
},
"maximum": 100,
"type": "array",
"x-stream-index": "011"
},
"settings_override": {
"$ref": "#/components/schemas/CallSettingsRequest",
"x-stream-index": "012"
},
"starts_at": {
"format": "date-time",
"type": "string",
"x-stream-index": "013"
},
"team": {
"type": "string",
"x-stream-index": "007"
}
},
"type": "object"
},
"MemberRequest": {
"properties": {
"custom": {
"additionalProperties": {},
"description": "Custom data for this object",
"title": "Custom data",
"type": "object",
"x-stream-index": "003"
},
"role": {
"title": "Role",
"type": "string",
"x-stream-index": "002"
},
"user_id": {
"minLength": 1,
"title": "User ID",
"type": "string",
"x-stream-index": "001"
}
},
"required": [
"user_id"
],
"type": "object"
},
"OwnCapability": {
"description": "All possibility of string to use",
"enum": [
"block-users",
"create-call",
"create-reaction",
"end-call",
"join-backstage",
"join-call",
"join-ended-call",
"mute-users",
"pin-for-everyone",
"read-call",
"remove-call-member",
"screenshare",
"send-audio",
"send-video",
"start-broadcast-call",
"start-record-call",
"start-transcription-call",
"stop-broadcast-call",
"stop-record-call",
"stop-transcription-call",
"update-call",
"update-call-member",
"update-call-permissions",
"update-call-settings"
],
"title": "OwnCapability",
"type": "string"
},
"UpdateCallResponse": {
"description": "Represents a call",
"nullable": true,
"properties": {
"call": {
"$ref": "#/components/schemas/CallResponse",
"x-stream-index": "001.001"
},
"duration": {
"type": "string",
"x-stream-index": "002.001"
},
"members": {
"items": {
"$ref": "#/components/schemas/MemberResponse"
},
"type": "array",
"x-stream-index": "001.002"
},
"membership": {
"$ref": "#/components/schemas/MemberResponse",
"x-stream-index": "001.003"
},
"own_capabilities": {
"items": {
"$ref": "#/components/schemas/OwnCapability"
},
"type": "array",
"x-stream-index": "001.004"
}
},
"required": [
"call",
"members",
"own_capabilities",
"duration"
],
"title": "Call",
"type": "object"
}
}
}
}
Loading

0 comments on commit 29bfae9

Please sign in to comment.