268 lines
9.1 KiB
TypeScript
268 lines
9.1 KiB
TypeScript
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;
|
|
}
|
|
|
|
const meta_fields = ["label", "created_at", "created_by", "updated_at", "updated_by"]
|
|
|
|
export const jsonschemaProvider = {
|
|
getCardResourceSchema: async (resourceName: string): Promise<CrudRJSFSchema> => {
|
|
const updateSchema = await getResourceSchema(`${resourceName}Update`);
|
|
const readSchema = await getResourceSchema(`${resourceName}Read`);
|
|
|
|
for (let prop_name in readSchema.properties) {
|
|
if (meta_fields.indexOf(prop_name) > -1) {
|
|
delete readSchema.properties[prop_name];
|
|
} else 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
|
|
},
|
|
|
|
getReadOnlyResourceSchema: async (resourceName: string): Promise<CrudRJSFSchema> => {
|
|
const updateSchema = await getResourceSchema(`${resourceName}Update`);
|
|
const readSchema = await getResourceSchema(`${resourceName}Read`);
|
|
|
|
for (let prop_name in readSchema.properties) {
|
|
if (updateSchema.hasOwnProperty(prop_name)) {
|
|
delete readSchema.properties[prop_name];
|
|
} else {
|
|
readSchema.properties[prop_name].readOnly = true;
|
|
}
|
|
}
|
|
|
|
return readSchema
|
|
},
|
|
|
|
getUpdateResourceSchema: async (resourceName: string): Promise<CrudRJSFSchema> => {
|
|
return getResourceSchema(`${resourceName}Update`)
|
|
},
|
|
|
|
getCreateResourceSchema: async (resourceName: string): Promise<CrudRJSFSchema> => {
|
|
return getResourceSchema(`${resourceName}Create`)
|
|
},
|
|
}
|
|
|
|
const getResourceSchema = async (resourceName: string): Promise<CrudRJSFSchema> => {
|
|
return buildResource(await getJsonschema(), resourceName)
|
|
}
|
|
|
|
let rawSchema: RJSFSchema;
|
|
const getJsonschema = async (): Promise<RJSFSchema> => {
|
|
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)
|
|
);
|
|
}
|