new react-openapi
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { OpenApiSpec, ResourceConfig, FieldConfig } from "../types";
|
||||
import { extractFields } from "./field-config";
|
||||
import { extractFields, clearValidationErrors, getValidationErrors } from "./field-config";
|
||||
import { extractRelationships } from "./relationship-config";
|
||||
|
||||
function detectPagination(pathObj: any): { limitParam: string; offsetParam: string; defaultLimit: number } | null {
|
||||
@@ -47,62 +47,147 @@ const SSE_RECEIVED_FIELD: FieldConfig = {
|
||||
isArray: false,
|
||||
};
|
||||
|
||||
function getSegments(path: string): string[] {
|
||||
return path.split("/").filter(Boolean);
|
||||
}
|
||||
|
||||
function getResponseSchemaRef(pathObj: any): string | undefined {
|
||||
const response = pathObj?.get?.responses?.["200"] ?? pathObj?.get?.responses?.["201"];
|
||||
const content = response?.content;
|
||||
if (!content) return;
|
||||
for (const mediaType of Object.values(content) as any[]) {
|
||||
if (mediaType?.schema?.$ref) return mediaType.schema.$ref;
|
||||
if (mediaType?.schema?.items?.$ref) return mediaType.schema.items.$ref;
|
||||
if (mediaType?.schema?.properties?.items?.items?.$ref) return mediaType.schema.properties.items.items.$ref;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveRef(ref: string): string {
|
||||
return ref.split("/").pop()!;
|
||||
}
|
||||
|
||||
export function buildResourceConfigs(spec: OpenApiSpec): ResourceConfig[] {
|
||||
clearValidationErrors();
|
||||
const schemas = spec.components?.schemas ?? {};
|
||||
const paths = spec.paths ?? {};
|
||||
const configs: ResourceConfig[] = [];
|
||||
const nameMap = new Map<string, ResourceConfig>();
|
||||
|
||||
for (const [schemaName, schema] of Object.entries(schemas)) {
|
||||
if (!schema || typeof schema !== "object") continue;
|
||||
const sortedPaths = Object.keys(paths).sort(
|
||||
(a, b) => getSegments(a).length - getSegments(b).length
|
||||
);
|
||||
|
||||
const resourceName = schema["x-resource"];
|
||||
if (!resourceName || typeof resourceName !== "string") continue;
|
||||
for (const path of sortedPaths) {
|
||||
const segments = getSegments(path);
|
||||
const pathObj = paths[path];
|
||||
const lastSeg = segments[segments.length - 1];
|
||||
const isItemPath = /^\{.*\}$/.test(lastSeg);
|
||||
const paramIdx = segments.findIndex(
|
||||
(s, i) => i < segments.length - 1 && /^\{.*\}$/.test(s)
|
||||
);
|
||||
|
||||
const resourcePath = `/${resourceName}`;
|
||||
const itemPath = `${resourcePath}/{id}`;
|
||||
const collectionPathObj = paths[resourcePath];
|
||||
const itemPathObj = paths[itemPath];
|
||||
if (isItemPath) {
|
||||
const parentName = segments[segments.length - 2];
|
||||
const parent = nameMap.get(parentName);
|
||||
if (!parent) continue;
|
||||
if (hasOperation(pathObj, "get")) parent.operations.get = true;
|
||||
if (hasOperation(pathObj, "put") || hasOperation(pathObj, "patch")) parent.operations.update = true;
|
||||
if (hasOperation(pathObj, "delete")) parent.operations.delete = true;
|
||||
if (hasOperation(pathObj, "patch") && !hasOperation(pathObj, "put")) parent.updateMethod = "patch";
|
||||
continue;
|
||||
}
|
||||
|
||||
const fields = extractFields(schemaName, schema, schemas);
|
||||
const relationships = extractRelationships(schema, schemas);
|
||||
const hasSSE = collectionPathObj?.get?.["x-sse"] === true;
|
||||
if (paramIdx >= 0) {
|
||||
const resourceName = lastSeg;
|
||||
const parentName = segments[paramIdx - 1];
|
||||
const pathParamName = segments[paramIdx].replace(/[{}]/g, "");
|
||||
|
||||
const responseRef = getResponseSchemaRef(pathObj);
|
||||
const schemaName = responseRef ? resolveRef(responseRef) : undefined;
|
||||
const schema = schemaName ? schemas[schemaName] : undefined;
|
||||
|
||||
const fields = schema ? extractFields(schemaName!, schema, schemas) : [];
|
||||
const hasSSE = pathObj?.get?.["x-sse"] === true;
|
||||
|
||||
const resource: ResourceConfig = {
|
||||
name: resourceName,
|
||||
schemaName: schemaName ?? resourceName,
|
||||
displayName: formatDisplayName(resourceName),
|
||||
path,
|
||||
primaryKey: schema?.["x-primary-key"] ?? "_received_at",
|
||||
displayFormat: schema?.["x-display-format"] ?? `{${resourceName}}`,
|
||||
listColumns: schema?.["x-list-columns"] ?? [],
|
||||
fields: hasSSE ? [SSE_RECEIVED_FIELD, ...fields.map((f) => ({ ...f, readOnly: true }))] : fields,
|
||||
orderedFields: [],
|
||||
operations: hasSSE
|
||||
? { list: true, get: false, create: false, update: false, delete: false }
|
||||
: { list: hasOperation(pathObj, "get"), get: false, create: false, update: false, delete: false },
|
||||
updateMethod: "put",
|
||||
pagination: hasSSE ? null : detectPagination(pathObj),
|
||||
relationships: [],
|
||||
streaming: hasSSE || undefined,
|
||||
parent: { resource: parentName, pathParam: pathParamName },
|
||||
};
|
||||
|
||||
resource.orderedFields = sortFields(resource.fields);
|
||||
if (hasSSE) {
|
||||
resource.listColumns = ["_received_at", ...resource.listColumns];
|
||||
resource.primaryKey = "_received_at";
|
||||
}
|
||||
|
||||
const parent = nameMap.get(parentName);
|
||||
if (parent) {
|
||||
parent.subResources = parent.subResources ?? [];
|
||||
parent.subResources.push(resourceName);
|
||||
}
|
||||
|
||||
nameMap.set(resourceName, resource);
|
||||
configs.push(resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
const resourceName = lastSeg;
|
||||
const responseRef = getResponseSchemaRef(pathObj);
|
||||
const schemaName = responseRef ? resolveRef(responseRef) : undefined;
|
||||
const schema = schemaName ? schemas[schemaName] : undefined;
|
||||
|
||||
const fields = schema ? extractFields(schemaName!, schema, schemas) : [];
|
||||
const relationships = schema ? extractRelationships(schema, schemas) : [];
|
||||
const hasSSE = pathObj?.get?.["x-sse"] === true;
|
||||
|
||||
const resource: ResourceConfig = {
|
||||
name: resourceName,
|
||||
schemaName,
|
||||
schemaName: schemaName ?? resourceName,
|
||||
displayName: formatDisplayName(resourceName),
|
||||
path: resourcePath,
|
||||
primaryKey: schema["x-primary-key"],
|
||||
displayFormat: schema["x-display-format"],
|
||||
listColumns: schema["x-list-columns"],
|
||||
fields,
|
||||
orderedFields: sortFields(fields),
|
||||
operations: {
|
||||
list: hasOperation(collectionPathObj, "get"),
|
||||
get: hasOperation(itemPathObj, "get"),
|
||||
create: hasOperation(collectionPathObj, "post"),
|
||||
update: hasOperation(itemPathObj, "put") || hasOperation(itemPathObj, "patch"),
|
||||
delete: hasOperation(itemPathObj, "delete"),
|
||||
},
|
||||
updateMethod: hasOperation(itemPathObj, "patch") && !hasOperation(itemPathObj, "put") ? "patch" : "put",
|
||||
pagination: detectPagination(collectionPathObj),
|
||||
path,
|
||||
primaryKey: schema?.["x-primary-key"] ?? "id",
|
||||
displayFormat: schema?.["x-display-format"] ?? `{${resourceName}}`,
|
||||
listColumns: schema?.["x-list-columns"] ?? [],
|
||||
fields: hasSSE ? [SSE_RECEIVED_FIELD, ...fields.map((f) => ({ ...f, readOnly: true }))] : fields,
|
||||
orderedFields: [],
|
||||
operations: hasSSE
|
||||
? { list: true, get: false, create: false, update: false, delete: false }
|
||||
: { list: hasOperation(pathObj, "get"), get: false, create: hasOperation(pathObj, "post"), update: false, delete: false },
|
||||
updateMethod: "put",
|
||||
pagination: hasSSE ? null : detectPagination(pathObj),
|
||||
relationships,
|
||||
streaming: hasSSE || undefined,
|
||||
};
|
||||
|
||||
resource.orderedFields = sortFields(resource.fields);
|
||||
if (hasSSE) {
|
||||
resource.operations = { list: true, get: false, create: false, update: false, delete: false };
|
||||
resource.updateMethod = "put";
|
||||
resource.pagination = null;
|
||||
resource.relationships = [];
|
||||
resource.fields = [SSE_RECEIVED_FIELD, ...fields.map((f) => ({ ...f, readOnly: true }))];
|
||||
resource.orderedFields = sortFields(resource.fields);
|
||||
resource.listColumns = ["_received_at", ...resource.listColumns];
|
||||
resource.primaryKey = "_received_at";
|
||||
}
|
||||
|
||||
nameMap.set(resourceName, resource);
|
||||
configs.push(resource);
|
||||
}
|
||||
|
||||
const errors = getValidationErrors();
|
||||
if (errors.length > 0) {
|
||||
throw new Error(errors.join("\n"));
|
||||
}
|
||||
|
||||
return configs;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user