import { Observable } from "rxjs"; import { Injectable } from "@angular/core"; import { ApiService } from "./crud.service"; import {JSONSchema7} from "json-schema"; @Injectable({ providedIn: 'root', }) export class JsonschemasService { private rawSchemas: any | null = null; constructor( private apiService: ApiService ) {} getSchemas(): Observable { return new Observable((observer) => { if (this.rawSchemas === null) { this.apiService.getSchema().subscribe((jsonSchemas: any) => { this.rawSchemas = jsonSchemas; observer.next(this.rawSchemas) }); } else { observer.next(this.rawSchemas) } }); } buildResource(resourceName: string) { let resource; resource = structuredClone(this.rawSchemas.components.schemas[resourceName]); resource.components = { schemas: {} }; for (let prop_name in resource.properties) { let prop = resource.properties[prop_name]; if (prop_name === '_id') { delete resource.properties[prop_name] } else if (this.is_reference(prop)) { this.resolveReference(resource, prop); } else if (this.is_union(prop)) { for (let i in prop.oneOf) { this.resolveReference(resource, prop.oneOf[i]); } } else if (this.is_enum(prop)) { for (let i in prop.allOf) { this.resolveReference(resource, prop.allOf[i]); } } else if (this.is_array(prop) && this.is_reference(prop.items)) { this.resolveReference(resource, prop.items); } } return resource; } resolveReference(resource: any, prop_reference: any) { let subresourceName = this.get_reference_name(prop_reference); let subresource = this.buildResource(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]; } } } getReferencedSchema() { } getCreateResource(resourceName: string): Observable { return new Observable((observer) => { this.getSchemas().subscribe(() => { observer.next(this.buildResource(resourceName + 'Create')) }) }) } getUpdateResource(resourceName: string): Observable { return new Observable((observer) => { this.getSchemas().subscribe(() => { let resource = this.buildResource(resourceName + 'Read'); let updateResource = this.rawSchemas.components.schemas[resourceName + 'Update'] for (let prop_name in resource.properties) { if (! updateResource.properties.hasOwnProperty(prop_name)) { if (this.is_reference(resource.properties[prop_name])) { let subresourceName = this.get_reference_name(resource.properties[prop_name]); resource.components.schemas[subresourceName].readOnly = true; } else { resource.properties[prop_name].readOnly = true; } } } this.changePropertiesOrder(resource); observer.next(resource); }) }) } changePropertiesOrder(resource: any) { let created_at; let updated_at; 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 == 'updated_at') { updated_at = resource.properties[prop_name]; } else { new_properties[prop_name] = resource.properties[prop_name]; } } if (created_at) { new_properties['created_at'] = created_at; } if (updated_at) { new_properties['updated_at'] = updated_at; } resource.properties = new_properties } private is_object(prop: any) { return prop.hasOwnProperty('properties') } private is_reference(prop: any) { return prop.hasOwnProperty('$ref'); } private is_array(prop: any) { return prop.hasOwnProperty('items'); } private is_union(prop: any) { return prop.hasOwnProperty('oneOf'); } private is_enum(prop: any) { return prop.hasOwnProperty('allOf'); } private get_reference_name(prop: any) { return prop['$ref'].substring(prop['$ref'].lastIndexOf('/')+1); } has_descendant(resource: JSONSchema7, property_name: string): boolean { if (this.is_array(resource)) { return property_name == 'items'; } else if (this.is_object(resource)) { return property_name in resource.properties!; } else if (this.is_reference(resource)) { let subresourceName = this.get_reference_name(resource); return this.has_descendant(this.buildResource(subresourceName), property_name); } else if (this.is_union(resource)) { for (const ref of resource.oneOf!) { // @ts-ignore if (this.has_descendant(ref, property_name)) { return true; } return false; } } else if (this.is_enum(resource)) { for (const ref of resource.allOf!) { // @ts-ignore if (this.has_descendant(ref, property_name)) { return true; } return false; } } throw new Error("Jsonschema format not implemented in property finder"); return false; } get_descendant(resource: JSONSchema7, property_name: string): JSONSchema7 { if (this.is_array(resource) && property_name == 'items') { // @ts-ignore return resource.items; } else if (this.is_object(resource) && property_name in resource.properties!) { // @ts-ignore return resource.properties[property_name]; } else if (this.is_reference(resource)) { let subresourceName = this.get_reference_name(resource); let subresource = this.buildResource(subresourceName); return this.get_descendant(subresource, property_name); } else if (this.is_union(resource)) { for (const ref of resource.oneOf!) { // @ts-ignore if (this.has_descendant(ref, property_name)) { // @ts-ignore return this.get_descendant(ref, property_name); } } } else if (this.is_enum(resource)) { for (const ref of resource.allOf!) { // @ts-ignore if (this.has_descendant(ref, property_name)) { // @ts-ignore return this.get_descendant(ref, property_name); } } } throw new Error("property not found or Jsonschema format not implemented"); } path_exists(resource: JSONSchema7, path: string): boolean{ const pointFirstPosition = path.indexOf('.') if (pointFirstPosition == -1) { return this.has_descendant(resource, path); } return this.has_descendant(resource, path.substring(0, pointFirstPosition)) && this.path_exists( this.get_descendant( resource, path.substring(0, pointFirstPosition) ), path.substring(pointFirstPosition + 1) ); } get_property_by_path(resource: JSONSchema7, path: string): JSONSchema7 { const pointFirstPosition = path.indexOf('.') if (pointFirstPosition == -1) { return this.get_descendant(resource, path); } return this.get_property_by_path( this.get_descendant( resource, path.substring(0, pointFirstPosition) ), path.substring(pointFirstPosition + 1) ); } } export interface Schema { components: { }; }