@@ -18,16 +18,18 @@ import { Logger } from '../Logger'
18
18
// Create a single-file OpenAPI spec from multiple files for OpenAPI validation and programmatic consumption
19
19
export default class OpenApiMerger {
20
20
root_folder : string
21
- spec : Record < string , any >
22
21
logger : Logger
23
22
23
+ protected _spec : Record < string , any >
24
+ protected _merged : boolean = false
25
+
24
26
paths : Record < string , Record < string , OpenAPIV3 . PathItemObject > > = { } // namespace -> path -> path_item_object
25
27
schemas : Record < string , Record < string , OpenAPIV3 . SchemaObject > > = { } // category -> schema -> schema_object
26
28
27
29
constructor ( root_folder : string , logger : Logger = new Logger ( ) ) {
28
30
this . logger = logger
29
31
this . root_folder = fs . realpathSync ( root_folder )
30
- this . spec = {
32
+ this . _spec = {
31
33
openapi : '3.1.0' ,
32
34
info : read_yaml ( `${ this . root_folder } /_info.yaml` , true ) ,
33
35
paths : { } ,
@@ -40,17 +42,23 @@ export default class OpenApiMerger {
40
42
}
41
43
}
42
44
43
- merge ( output_path : string = '' ) : OpenAPIV3 . Document {
44
- this . #merge_schemas( )
45
- this . #merge_namespaces( )
46
- this . #sort_spec_keys( )
47
- this . #generate_global_params( )
48
- this . #generate_superseded_ops( )
49
-
45
+ write_to ( output_path : string ) : OpenApiMerger {
50
46
this . logger . info ( `Writing ${ output_path } ...` )
47
+ write_yaml ( output_path , this . spec ( ) )
48
+ return this
49
+ }
50
+
51
+ spec ( ) : OpenAPIV3 . Document {
52
+ if ( ! this . _merged ) {
53
+ this . #merge_schemas( )
54
+ this . #merge_namespaces( )
55
+ this . #sort_spec_keys( )
56
+ this . #generate_global_params( )
57
+ this . #generate_superseded_ops( )
58
+ this . _merged = true
59
+ }
51
60
52
- if ( output_path !== '' ) write_yaml ( output_path , this . spec )
53
- return this . spec as OpenAPIV3 . Document
61
+ return this . _spec as OpenAPIV3 . Document
54
62
}
55
63
56
64
// Merge files from <spec_root>/namespaces folder.
@@ -59,21 +67,21 @@ export default class OpenApiMerger {
59
67
fs . readdirSync ( folder ) . forEach ( file => {
60
68
this . logger . info ( `Merging namespaces in ${ folder } /${ file } ...` )
61
69
const spec = read_yaml ( `${ folder } /${ file } ` )
62
- this . redirect_refs_in_namespace ( spec )
63
- this . spec . paths = { ...this . spec . paths , ...spec . paths }
64
- this . spec . components . parameters = { ...this . spec . components . parameters , ...spec . components . parameters }
65
- this . spec . components . responses = { ...this . spec . components . responses , ...spec . components . responses }
66
- this . spec . components . requestBodies = { ...this . spec . components . requestBodies , ...spec . components . requestBodies }
70
+ this . # redirect_refs_in_namespace( spec )
71
+ this . _spec . paths = { ...this . _spec . paths , ...spec . paths }
72
+ this . _spec . components . parameters = { ...this . _spec . components . parameters , ...spec . components . parameters }
73
+ this . _spec . components . responses = { ...this . _spec . components . responses , ...spec . components . responses }
74
+ this . _spec . components . requestBodies = { ...this . _spec . components . requestBodies , ...spec . components . requestBodies }
67
75
} )
68
76
}
69
77
70
78
// Redirect schema references in namespace files to local references in single-file spec.
71
- redirect_refs_in_namespace ( obj : any ) : void {
79
+ # redirect_refs_in_namespace ( obj : any ) : void {
72
80
const ref : string = obj ?. $ref
73
81
if ( ref ?. startsWith ( '../schemas/' ) ) { obj . $ref = ref . replace ( '../schemas/' , '#/components/schemas/' ) . replace ( '.yaml#/components/schemas/' , ':' ) }
74
82
75
83
for ( const key in obj ) {
76
- if ( typeof obj [ key ] === 'object' ) { this . redirect_refs_in_namespace ( obj [ key ] ) }
84
+ if ( typeof obj [ key ] === 'object' ) { this . # redirect_refs_in_namespace( obj [ key ] ) }
77
85
}
78
86
}
79
87
@@ -92,7 +100,7 @@ export default class OpenApiMerger {
92
100
93
101
Object . entries ( this . schemas ) . forEach ( ( [ category , schemas ] ) => {
94
102
Object . entries ( schemas ) . forEach ( ( [ name , schema_obj ] ) => {
95
- this . spec . components . schemas [ `${ category } :${ name } ` ] = schema_obj
103
+ this . _spec . components . schemas [ `${ category } :${ name } ` ] = schema_obj
96
104
} )
97
105
} )
98
106
}
@@ -115,26 +123,26 @@ export default class OpenApiMerger {
115
123
116
124
// Sort keys in the spec to make it easier to read and compare.
117
125
#sort_spec_keys ( ) : void {
118
- this . spec . components . schemas = _ . fromPairs ( Object . entries ( this . spec . components . schemas as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
119
- this . spec . components . parameters = _ . fromPairs ( Object . entries ( this . spec . components . parameters as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
120
- this . spec . components . responses = _ . fromPairs ( Object . entries ( this . spec . components . responses as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
121
- this . spec . components . requestBodies = _ . fromPairs ( Object . entries ( this . spec . components . requestBodies as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
122
-
123
- this . spec . paths = _ . fromPairs ( Object . entries ( this . spec . paths as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
124
- Object . entries ( this . spec . paths as Document ) . forEach ( ( [ path , path_item ] ) => {
125
- this . spec . paths [ path ] = _ . fromPairs ( Object . entries ( path_item as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
126
+ this . _spec . components . schemas = _ . fromPairs ( Object . entries ( this . _spec . components . schemas as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
127
+ this . _spec . components . parameters = _ . fromPairs ( Object . entries ( this . _spec . components . parameters as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
128
+ this . _spec . components . responses = _ . fromPairs ( Object . entries ( this . _spec . components . responses as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
129
+ this . _spec . components . requestBodies = _ . fromPairs ( Object . entries ( this . _spec . components . requestBodies as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
130
+
131
+ this . _spec . paths = _ . fromPairs ( Object . entries ( this . _spec . paths as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
132
+ Object . entries ( this . _spec . paths as Document ) . forEach ( ( [ path , path_item ] ) => {
133
+ this . _spec . paths [ path ] = _ . fromPairs ( Object . entries ( path_item as Document ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) )
126
134
} )
127
135
}
128
136
129
137
// Generate global parameters from _global_params.yaml file.
130
138
#generate_global_params ( ) : void {
131
139
const gen = new GlobalParamsGenerator ( this . root_folder )
132
- gen . generate ( this . spec )
140
+ gen . generate ( this . _spec )
133
141
}
134
142
135
143
// Generate superseded operations from _superseded_operations.yaml file.
136
144
#generate_superseded_ops ( ) : void {
137
145
const gen = new SupersededOpsGenerator ( this . root_folder , this . logger )
138
- gen . generate ( this . spec )
146
+ gen . generate ( this . _spec )
139
147
}
140
148
}
0 commit comments