@@ -43,7 +43,7 @@ import { ExtendedSBVRParser } from '../extended-sbvr-parser/extended-sbvr-parser
4343
4444import * as asyncMigrator from '../migrator/async' ;
4545import * as syncMigrator from '../migrator/sync' ;
46- import { generateODataMetadata } from '../odata-metadata/odata-metadata -generator' ;
46+ import { generateODataMetadataAsOpenApi } from '../odata-metadata/open-api-sepcification -generator' ;
4747
4848import type DevModel from './dev' ;
4949// eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -105,6 +105,8 @@ import {
105105 type MigrationExecutionResult ,
106106 setExecutedMigrations ,
107107} from '../migrator/utils' ;
108+ import { generateODataMetadata } from '../odata-metadata/odata-metadata-generator' ;
109+ import { metadataEndpoints } from './uri-parser' ;
108110
109111const LF2AbstractSQLTranslator = LF2AbstractSQL . createTranslator ( sbvrTypes ) ;
110112const LF2AbstractSQLTranslatorVersion = `${ LF2AbstractSQLVersion } +${ sbvrTypesVersion } ` ;
@@ -147,18 +149,18 @@ export interface ApiKey extends Actor {
147149export interface Response {
148150 statusCode : number ;
149151 headers ?:
150- | {
151- [ headerName : string ] : any ;
152- }
153- | undefined ;
152+ | {
153+ [ headerName : string ] : any ;
154+ }
155+ | undefined ;
154156 body ?: AnyObject | string ;
155157}
156158
157159export type ModelExecutionResult =
158160 | undefined
159161 | {
160- migrationExecutionResult ?: MigrationExecutionResult ;
161- } ;
162+ migrationExecutionResult ?: MigrationExecutionResult ;
163+ } ;
162164
163165const memoizedResolvedSynonym = memoizeWeak (
164166 (
@@ -250,9 +252,9 @@ const prettifyConstraintError = (
250252 let keyMatches : RegExpExecArray | null = null ;
251253 let violatedConstraintInfo :
252254 | {
253- table : AbstractSQLCompiler . AbstractSqlTable ;
254- name : string ;
255- }
255+ table : AbstractSQLCompiler . AbstractSqlTable ;
256+ name : string ;
257+ }
256258 | undefined ;
257259 if ( err instanceof db . UniqueConstraintError ) {
258260 switch ( db . engine ) {
@@ -290,8 +292,8 @@ const prettifyConstraintError = (
290292 const columns = keyMatches [ 1 ] . split ( '_' ) ;
291293 throw new db . UniqueConstraintError (
292294 '"' +
293- columns . map ( sqlNameToODataName ) . join ( '" and "' ) +
294- '" must be unique.' ,
295+ columns . map ( sqlNameToODataName ) . join ( '" and "' ) +
296+ '" must be unique.' ,
295297 ) ;
296298 }
297299 if ( violatedConstraintInfo != null ) {
@@ -326,16 +328,16 @@ const prettifyConstraintError = (
326328 const tableName = abstractSqlModel . tables [ resourceName ] . name ;
327329 keyMatches = new RegExp (
328330 '"' +
329- tableName +
330- '" violates foreign key constraint ".*?" on table "(.*?)"' ,
331+ tableName +
332+ '" violates foreign key constraint ".*?" on table "(.*?)"' ,
331333 ) . exec ( err . message ) ;
332334 if ( keyMatches == null ) {
333335 keyMatches = new RegExp (
334336 '"' +
335- tableName +
336- '" violates foreign key constraint "' +
337- tableName +
338- '_(.*?)_fkey"' ,
337+ tableName +
338+ '" violates foreign key constraint "' +
339+ tableName +
340+ '_(.*?)_fkey"' ,
339341 ) . exec ( err . message ) ;
340342 }
341343 break ;
@@ -362,8 +364,8 @@ const prettifyConstraintError = (
362364 case 'postgres' :
363365 keyMatches = new RegExp (
364366 'new row for relation "' +
365- table . name +
366- '" violates check constraint "(.*?)"' ,
367+ table . name +
368+ '" violates check constraint "(.*?)"' ,
367369 ) . exec ( err . message ) ;
368370 break ;
369371 }
@@ -980,11 +982,11 @@ export const runRule = (() => {
980982 const odataResult = ( await runURI (
981983 'GET' ,
982984 '/' +
983- vocab +
984- '/' +
985- sqlNameToODataName ( table . resourceName ) +
986- '?$filter=' +
987- filter ,
985+ vocab +
986+ '/' +
987+ sqlNameToODataName ( table . resourceName ) +
988+ '?$filter=' +
989+ filter ,
988990 undefined ,
989991 undefined ,
990992 permissions . rootRead ,
@@ -1375,7 +1377,7 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
13751377 - '#canAccess' . length ,
13761378 ) ;
13771379 }
1378- if ( abstractSqlModel . tables [ resolvedResourceName ] == null ) {
1380+ if ( abstractSqlModel . tables [ resolvedResourceName ] == null && ! metadataEndpoints . includes ( resolvedResourceName ) ) {
13791381 throw new UnauthorizedError ( ) ;
13801382 }
13811383
@@ -1695,19 +1697,19 @@ const runRequest = async (
16951697
16961698const runChangeSet =
16971699 ( req : Express . Request , tx : Db . Tx ) =>
1698- async (
1699- changeSetResults : Map < number , Response > ,
1700- request : uriParser . ODataRequest ,
1701- ) : Promise < void > => {
1702- request = updateBinds ( changeSetResults , request ) ;
1703- const result = await runRequest ( req , tx , request ) ;
1704- if ( request . id == null ) {
1705- throw new Error ( 'No request id' ) ;
1706- }
1707- result . headers ??= { } ;
1708- result . headers [ 'content-id' ] = request . id ;
1709- changeSetResults . set ( request . id , result ) ;
1710- } ;
1700+ async (
1701+ changeSetResults : Map < number , Response > ,
1702+ request : uriParser . ODataRequest ,
1703+ ) : Promise < void > => {
1704+ request = updateBinds ( changeSetResults , request ) ;
1705+ const result = await runRequest ( req , tx , request ) ;
1706+ if ( request . id == null ) {
1707+ throw new Error ( 'No request id' ) ;
1708+ }
1709+ result . headers ??= { } ;
1710+ result . headers [ 'content-id' ] = request . id ;
1711+ changeSetResults . set ( request . id , result ) ;
1712+ } ;
17111713
17121714// Requests inside a changeset may refer to resources created inside the
17131715// changeset, the generation of the sql query for those requests must be
@@ -1878,10 +1880,35 @@ const respondGet = async (
18781880 return response ;
18791881 } else {
18801882 if ( request . resourceName === '$metadata' ) {
1883+ const permLookup = await permissions . getReqPermissions ( req ) ;
1884+ const spec = generateODataMetadata (
1885+ vocab ,
1886+ models [ vocab ] . abstractSql ,
1887+ permLookup ,
1888+ ) ;
1889+ return {
1890+ statusCode : 200 ,
1891+ body : spec ,
1892+ headers : { 'content-type' : 'application/json' } ,
1893+ } ;
1894+ } else if ( request . resourceName === 'openapi.json' ) {
1895+ // https://docs.oasis-open.org/odata/odata-openapi/v1.0/cn01/odata-openapi-v1.0-cn01.html#sec_ProvidingOASDocumentsforanODataServi
1896+ // Following the OASIS OData to openapi translation guide the openapi.json is an independent resource
1897+ const permLookup = await permissions . getReqPermissions ( req ) ;
1898+ const spec = generateODataMetadata (
1899+ vocab ,
1900+ models [ vocab ] . abstractSql ,
1901+ permLookup ,
1902+ ) ;
1903+ const openApispec = generateODataMetadataAsOpenApi (
1904+ spec ,
1905+ req . originalUrl . replace ( 'openapi.json' , '' ) ,
1906+ req . hostname ,
1907+ ) ;
18811908 return {
18821909 statusCode : 200 ,
1883- body : models [ vocab ] . odataMetadata ,
1884- headers : { 'content-type' : 'xml ' } ,
1910+ body : openApispec ,
1911+ headers : { 'content-type' : 'application/json ' } ,
18851912 } ;
18861913 } else {
18871914 // TODO: request.resourceName can be '$serviceroot' or a resource and we should return an odata xml document based on that
0 commit comments