import { RJSFSchema } from '@rjsf/utils'; import i18n from '../../../i18n' import { JSONSchema7Definition } from "json-schema"; const API_URL = "/api/v1"; type CrudRJSFSchema = RJSFSchema & { properties?: { [key: string]: JSONSchema7Definition & { readOnly?: boolean | undefined; }; } | undefined; } export const jsonschemaProvider = { getCardResourceSchema: async (resourceName: string): Promise => { const updateSchema = await getResourceSchema(`${resourceName}Update`); const readSchema = await getResourceSchema(`${resourceName}Read`); for (let prop_name in readSchema.properties) { if (! updateSchema.hasOwnProperty(prop_name)) { if (is_reference(readSchema.properties[prop_name])) { let subresourceName = get_reference_name(readSchema.properties[prop_name]); readSchema.components.schemas[subresourceName].readOnly = true; } else { readSchema.properties[prop_name].readOnly = true; } } } changePropertiesOrder(readSchema); return readSchema }, getUpdateResourceSchema: async (resourceName: string): Promise => { return getResourceSchema(`${resourceName}Update`) }, getCreateResourceSchema: async (resourceName: string): Promise => { return getResourceSchema(`${resourceName}Create`) }, } const getResourceSchema = async (resourceName: string): Promise => { return buildResource(await getJsonschema(), resourceName) } let rawSchema: RJSFSchema; const getJsonschema = async (): Promise => { if (rawSchema === undefined) { const response = await fetch(`${API_URL}/openapi.json`,); rawSchema = await response.json(); } return rawSchema; } function convertCamelToSnake(str: string): string { return str.replace(/([a-zA-Z])(?=[A-Z])/g,'$1_').toLowerCase() } function buildResource(rawSchemas: RJSFSchema, resourceName: string) { if (rawSchemas.components.schemas[resourceName] === undefined) { throw new Error(`Resource "${resourceName}" not found in schema.`); } const shortResourceName = convertCamelToSnake(resourceName.replace(/(-Input|Create|Update)$/g, "")); let resource = structuredClone(rawSchemas.components.schemas[resourceName]); resource.components = { schemas: {} }; for (let prop_name in resource.properties) { let prop = resource.properties[prop_name]; if (is_reference(prop)) { resolveReference(rawSchemas, resource, prop); } else if (is_union(prop)) { const union = prop.hasOwnProperty("oneOf") ? prop.oneOf : prop.anyOf; for (let i in union) { if (is_reference(union[i])) { resolveReference(rawSchemas, resource, union[i]); } } } else if (is_enum(prop)) { for (let i in prop.allOf) { if (is_reference(prop.allOf[i])) { resolveReference(rawSchemas, resource, prop.allOf[i]); } } } else if (is_array(prop) && is_reference(prop.items)) { resolveReference(rawSchemas, resource, prop.items); } if (prop.hasOwnProperty("title")) { prop.title = i18n.t(`schemas.${shortResourceName}.${convertCamelToSnake(prop_name)}`, prop.title); } } if (resource.hasOwnProperty("title")) { resource.title = i18n.t(`schemas.${shortResourceName}.resource_title`, resource.title); } return resource; } function resolveReference(rawSchemas: RJSFSchema, resource: any, prop_reference: any) { const subresourceName = get_reference_name(prop_reference); const subresource = buildResource(rawSchemas, subresourceName); resource.components.schemas[subresourceName] = subresource; for (let subsubresourceName in subresource.components.schemas) { if (! resource.components.schemas.hasOwnProperty(subsubresourceName)) { resource.components.schemas[subsubresourceName] = subresource.components.schemas[subsubresourceName]; } } } function changePropertiesOrder(resource: any) { let created_at; let created_by; let updated_at; let updated_by; let new_properties: any = {}; for (let prop_name in resource.properties) { if (prop_name == 'created_at') { created_at = resource.properties[prop_name]; } else if (prop_name == 'created_by') { created_by = resource.properties[prop_name]; } else if (prop_name == 'updated_at') { updated_at = resource.properties[prop_name]; } else if (prop_name == 'updated_by') { updated_by = resource.properties[prop_name]; }else { new_properties[prop_name] = resource.properties[prop_name]; } } if (created_at) { new_properties['created_at'] = created_at; } if (created_by) { new_properties['created_by'] = created_by; } if (updated_at) { new_properties['updated_at'] = updated_at; } if (updated_by) { new_properties['updated_by'] = updated_by; } resource.properties = new_properties } function is_object(prop: any) { return prop.hasOwnProperty('properties') } function is_reference(prop: any) { return prop.hasOwnProperty('$ref'); } function is_array(prop: any) { return prop.hasOwnProperty('items'); } function is_union(prop: any) { return prop.hasOwnProperty('oneOf') || prop.hasOwnProperty('anyOf'); } function is_enum(prop: any) { return prop.hasOwnProperty('enum'); } function get_reference_name(prop: any) { return prop['$ref'].substring(prop['$ref'].lastIndexOf('/')+1); } function has_descendant(rawSchemas:RJSFSchema, resource: RJSFSchema, property_name: string): boolean { if (is_array(resource)) { return property_name == 'items'; } else if (is_object(resource)) { return property_name in resource.properties!; } else if (is_reference(resource)) { let subresourceName = get_reference_name(resource); return has_descendant(rawSchemas, buildResource(rawSchemas, subresourceName), property_name); } else if (is_union(resource)) { const union = resource.hasOwnProperty("oneOf") ? resource.oneOf : resource.anyOf; if (union !== undefined) { for (const ref of union) { return has_descendant(rawSchemas, ref as RJSFSchema, property_name) } } } else if (is_enum(resource)) { for (const ref of resource.allOf!) { return has_descendant(rawSchemas, ref as RJSFSchema, property_name); } } throw new Error("Jsonschema format not implemented in property finder"); } function get_descendant(rawSchemas: RJSFSchema, resource: RJSFSchema, property_name: string): RJSFSchema { if (is_array(resource) && property_name == 'items') { return resource.items as RJSFSchema; } else if (is_object(resource) && resource.properties !== undefined && property_name in resource.properties!) { return resource.properties[property_name] as RJSFSchema; } else if (is_reference(resource)) { let subresourceName = get_reference_name(resource); let subresource = buildResource(rawSchemas, subresourceName); return get_descendant(rawSchemas, subresource, property_name); } else if (is_union(resource)) { for (const ref of resource.oneOf!) { if (has_descendant(rawSchemas, ref as RJSFSchema, property_name)) { return get_descendant(rawSchemas, ref as RJSFSchema, property_name); } } } else if (is_enum(resource)) { for (const ref of resource.allOf!) { if (has_descendant(rawSchemas, ref as RJSFSchema, property_name)) { return get_descendant(rawSchemas, ref as RJSFSchema, property_name); } } } throw new Error("property not found or Jsonschema format not implemented"); } function path_exists(rawSchemas: RJSFSchema, resource: RJSFSchema, path: string): boolean{ const pointFirstPosition = path.indexOf('.') if (pointFirstPosition == -1) { return has_descendant(rawSchemas, resource, path); } return has_descendant(rawSchemas, resource, path.substring(0, pointFirstPosition)) && path_exists( rawSchemas, get_descendant(rawSchemas, resource, path.substring(0, pointFirstPosition)), path.substring(pointFirstPosition + 1) ); } function get_property_by_path(rawSchemas: RJSFSchema, resource: RJSFSchema, path: string): RJSFSchema { const pointFirstPosition = path.indexOf('.') if (pointFirstPosition == -1) { return get_descendant(rawSchemas, resource, path); } return get_property_by_path( rawSchemas, get_descendant( rawSchemas, resource, path.substring(0, pointFirstPosition) ), path.substring(pointFirstPosition + 1) ); }