@@ -16,7 +16,7 @@ import {
1616  transformCompareRules , 
1717}  from  '../core' 
1818import  type  {  OpenApi3SchemaRulesOptions  }  from  './openapi3.types' 
19- import  {  CompareRules  }  from  '../types' 
19+ import  {  CompareRules ,   AdapterResolver  }  from  '../types' 
2020import  { 
2121  JsonSchemaSpecVersion , 
2222  normalize , 
@@ -26,8 +26,15 @@ import {
2626  SPEC_TYPE_JSON_SCHEMA_07 , 
2727  SPEC_TYPE_OPEN_API_30 , 
2828  SPEC_TYPE_OPEN_API_31 , 
29+   JSON_SCHEMA_PROPERTY_NULLABLE , 
30+   JSON_SCHEMA_PROPERTY_ANY_OF , 
31+   JSON_SCHEMA_PROPERTY_ONE_OF , 
32+   JSON_SCHEMA_PROPERTY_TYPE , 
33+   JSON_SCHEMA_NODE_TYPE_NULL , 
34+   setOrigins , 
2935}  from  '@netcracker/qubership-apihub-api-unifier' 
3036import  {  schemaParamsCalculator  }  from  './openapi3.description.schema' 
37+ import  {  isObject ,  isArray  }  from  '../utils' 
3138
3239const  SPEC_TYPE_TO_VERSION : Record < OpenApiSpecVersion ,  string >  =  { 
3340  [ SPEC_TYPE_OPEN_API_30 ] : '3.0.0' , 
@@ -62,13 +69,138 @@ const openApiJsonSchemaAnyFactory: (version: OpenApiSpecVersion) => NativeAnySch
6269  return  normalizedSpec . components . schemas . empty  as  Record < PropertyKey ,  unknown > 
6370} 
6471
72+ // Helper function to check if a schema has null type 
73+ const  hasNullType  =  ( schema : unknown ) : boolean  =>  { 
74+   if  ( ! isObject ( schema ) )  return  false 
75+   
76+   // Check direct type property 
77+   const  type  =  schema [ JSON_SCHEMA_PROPERTY_TYPE ] 
78+   if  ( type  ===  JSON_SCHEMA_NODE_TYPE_NULL )  return  true 
79+   if  ( isArray ( type )  &&  type . includes ( JSON_SCHEMA_NODE_TYPE_NULL ) )  return  true 
80+   
81+   // Check in combiners (anyOf, oneOf) 
82+   for  ( const  combiner  of  [ JSON_SCHEMA_PROPERTY_ANY_OF ,  JSON_SCHEMA_PROPERTY_ONE_OF ]  as  const )  { 
83+     const  combinerArray  =  schema [ combiner ] 
84+     if  ( isArray ( combinerArray ) )  { 
85+       for  ( const  item  of  combinerArray )  { 
86+         if  ( hasNullType ( item ) )  return  true 
87+       } 
88+     } 
89+   } 
90+   
91+   return  false 
92+ } 
93+ 
94+ // Builds a "null" type schema carrying over relevant origins from source 
95+ // const buildNullTypeWithOrigins = ( 
96+ //   valueWithoutNullable: Record<PropertyKey, unknown>, 
97+ //   originsFlag: PropertyKey, 
98+ // ): Record<PropertyKey, unknown> => { 
99+ //   const nullTypeObject: Record<PropertyKey, unknown> = { 
100+ //     [JSON_SCHEMA_PROPERTY_TYPE]: JSON_SCHEMA_NODE_TYPE_NULL, 
101+ //   } 
102+ //   const valueOrigins = valueWithoutNullable[originsFlag] as Record<PropertyKey, unknown> | undefined 
103+ // 
104+ //   if (valueWithoutNullable.title) { 
105+ //     nullTypeObject.title = valueWithoutNullable.title 
106+ //     const titleOrigin = valueOrigins && (valueOrigins as Record<PropertyKey, unknown>).title 
107+ //     if (titleOrigin !== undefined) { 
108+ //       nullTypeObject[originsFlag] = { title: titleOrigin } 
109+ //     } 
110+ //   } 
111+ // 
112+ //   const nullableOrigin = valueOrigins && (valueOrigins as Record<PropertyKey, unknown>).nullable 
113+ //   if (nullableOrigin !== undefined) { 
114+ //     const existing = (nullTypeObject[originsFlag] as Record<PropertyKey, unknown>) || {} 
115+ //     nullTypeObject[originsFlag] = { ...existing, nullable: nullableOrigin } 
116+ //   } 
117+ // 
118+ //   return nullTypeObject 
119+ // } 
120+ 
121+ const  buildNullTypeWithOrigins  =  ( 
122+   valueWithoutNullable : Record < PropertyKey ,  unknown > , 
123+   originsFlag : PropertyKey , 
124+   syntheticTitleFlag : PropertyKey 
125+ ) : Record < PropertyKey ,  unknown >  =>  { 
126+   const  nullTypeObject : Record < PropertyKey ,  unknown >  =  { 
127+     [ JSON_SCHEMA_PROPERTY_TYPE ] : JSON_SCHEMA_NODE_TYPE_NULL , 
128+   } 
129+ 
130+   const  valueOrigins  =  valueWithoutNullable [ originsFlag ]  as  Record < PropertyKey ,  unknown >  |  undefined 
131+   const  origins : Record < PropertyKey ,  unknown >  =  { } 
132+ 
133+   if  ( valueWithoutNullable . title )  { 
134+     nullTypeObject . title  =  valueWithoutNullable . title 
135+     if  ( valueOrigins ?. title  !==  undefined )  { 
136+       origins . title  =  valueOrigins . title 
137+     } 
138+   } 
139+ 
140+   if  ( valueOrigins ?. nullable  !==  undefined )  { 
141+ 
142+     // origins.nullable = valueOrigins.nullable 
143+ 
144+     const  getOriginParent  =  ( item : unknown )  =>  ( { 
145+       value : JSON_SCHEMA_PROPERTY_TYPE , 
146+       parent : ( item  as  any ) ?. parent , 
147+     } ) 
148+ 
149+     origins [ JSON_SCHEMA_PROPERTY_TYPE ]  =  Array . isArray ( valueOrigins . nullable ) 
150+       ? valueOrigins . nullable . map ( getOriginParent ) 
151+       : getOriginParent ( valueOrigins . nullable ) 
152+   } 
153+ 
154+   if  ( Object . keys ( origins ) . length  >  0 )  { 
155+     nullTypeObject [ originsFlag ]  =  origins 
156+   } 
157+ 
158+   return  nullTypeObject 
159+ } 
160+ 
161+ // Adapter to transform JSON Schemas used in OpenAPI 3.0 schemas to JSON Schemas used in OpenAPI 3.1 for comparison 
162+ const  jsonSchemaOas30to31Adapter : AdapterResolver  =  ( value ,  reference ,  ctx )  =>  { 
163+   if  ( ! isObject ( value )  ||  ! isObject ( reference ) )  { 
164+     return  value 
165+   } 
166+ 
167+   if  ( value [ JSON_SCHEMA_PROPERTY_NULLABLE ]  !==  true )  { 
168+     return  value 
169+   } 
170+ 
171+   if  ( ! hasNullType ( reference ) )  { 
172+     return  value 
173+   } 
174+ 
175+   const  originsFlag : PropertyKey  =  ctx . options . originsFlag 
176+   // @ts -ignore 
177+   const  syntheticTitleFlag : PropertyKey  =  ctx . options . syntheticTitleFlag 
178+ 
179+   return  ctx . transformer ( value ,  'nullable-to-anyof' ,  ( current )  =>  { 
180+     const  {  [ JSON_SCHEMA_PROPERTY_NULLABLE ] : _nullable ,  ...valueWithoutNullable  }  =  current  as  Record < PropertyKey ,  unknown > 
181+ 
182+     const  nullTypeObject  =  buildNullTypeWithOrigins ( valueWithoutNullable ,  originsFlag ,  syntheticTitleFlag ) 
183+ 
184+     const  anyOfArray  =  [ valueWithoutNullable ,  nullTypeObject ] 
185+ 
186+     const  result : Record < PropertyKey ,  unknown >  =  {  [ JSON_SCHEMA_PROPERTY_ANY_OF ] : anyOfArray  } 
187+ 
188+     setOrigins ( result ,  JSON_SCHEMA_PROPERTY_ANY_OF ,  originsFlag ,  ctx . valueOrigins ) 
189+     setOrigins ( anyOfArray ,  0 ,  originsFlag ,  ctx . valueOrigins ) 
190+     setOrigins ( anyOfArray ,  1 ,  originsFlag ,  ctx . valueOrigins ) 
191+ 
192+     return  result 
193+   } ) 
194+ } 
195+ 
65196export  const  openApiSchemaRules  =  ( options : OpenApi3SchemaRulesOptions ) : CompareRules  =>  { 
66197  //todo find better way to remove this 'version specific' copy-paste with normalize 
67198  const  jsonSchemaVersion : JsonSchemaSpecVersion  =  options . version  ===  SPEC_TYPE_OPEN_API_30  ? SPEC_TYPE_JSON_SCHEMA_04  : SPEC_TYPE_JSON_SCHEMA_07 
68199
69200  const  schemaRules  =  jsonSchemaRules ( { 
70201    additionalRules : { 
71202      adapter : [ 
203+         ...( options . version  ===  SPEC_TYPE_OPEN_API_31  ? [ jsonSchemaOas30to31Adapter ]  : [ ] ) , 
72204        jsonSchemaAdapter ( openApiJsonSchemaAnyFactory ( options . version ) ) , 
73205      ] , 
74206      descriptionParamCalculator : schemaParamsCalculator , 
0 commit comments