55 */
66package io .jooby .internal .openapi ;
77
8- import java .io .IOException ;
9- import java .net .URL ;
10- import java .nio .file .Files ;
11- import java .nio .file .Path ;
12- import java .util .Collections ;
13- import java .util .List ;
14- import java .util .Optional ;
8+ import java .util .*;
9+ import java .util .function .BiConsumer ;
10+ import java .util .function .Consumer ;
11+ import java .util .function .Function ;
1512
1613import com .fasterxml .jackson .annotation .JsonIgnore ;
17- import io .jooby .SneakyThrows ;
18- import io .swagger .v3 .core . util . Yaml ;
19- import io .swagger .v3 .oas .models .OpenAPI ;
14+ import io .jooby .Router ;
15+ import io .swagger .v3 .oas . models .* ;
16+ import io .swagger .v3 .oas .models .parameters . Parameter ;
2017
2118public class OpenAPIExt extends OpenAPI {
2219 @ JsonIgnore private List <OperationExt > operations = Collections .emptyList ();
2320
2421 @ JsonIgnore private String source ;
2522
26- public static Optional <OpenAPI > fromTemplate (
27- Path basedir , ClassLoader classLoader , String templateName ) {
28- try {
29- Path path = basedir .resolve ("conf" ).resolve (templateName );
30- if (Files .exists (path )) {
31- return Optional .of (Yaml .mapper ().readValue (path .toFile (), OpenAPIExt .class ));
32- }
33- URL resource = classLoader .getResource (templateName );
34- if (resource != null ) {
35- return Optional .of (Yaml .mapper ().readValue (resource , OpenAPIExt .class ));
36- }
37- return Optional .empty ();
38- } catch (IOException x ) {
39- throw SneakyThrows .propagate (x );
40- }
41- }
42-
4323 public List <OperationExt > getOperations () {
4424 return operations ;
4525 }
@@ -55,4 +35,192 @@ public String getSource() {
5535 public void setSource (String classname ) {
5636 this .source = classname ;
5737 }
38+
39+ @ Override
40+ public void setPaths (Paths paths ) {
41+ var existingPaths = this .getPaths ();
42+ if (existingPaths != null && !existingPaths .isEmpty ()) {
43+ var mergePolicy =
44+ MergePolicy .parse (
45+ existingPaths .getExtensions (),
46+ MergePolicy .parse (getExtensions (), MergePolicy .IGNORE ));
47+ super .setPaths (mergePaths (existingPaths , paths , mergePolicy ));
48+ } else {
49+ super .setPaths (paths );
50+ }
51+ }
52+
53+ private Paths mergePaths (Paths docPaths , Paths paths , MergePolicy mergePolicy ) {
54+ for (var e : docPaths .entrySet ()) {
55+ var pattern = e .getKey ();
56+ var path = paths .get (pattern );
57+ if (path != null ) {
58+ // Copy into generated path
59+ var docPath = e .getValue ();
60+ setProperty (docPath , PathItem ::getSummary , path , PathItem ::setSummary );
61+ setProperty (docPath , PathItem ::getDescription , path , PathItem ::setDescription );
62+ setProperty (docPath , PathItem ::getServers , path , PathItem ::setServers );
63+ setProperty (docPath , PathItem ::getParameters , path , PathItem ::setParameters );
64+ setProperty (docPath , PathItem ::get$ref , path , PathItem ::set$ref );
65+ setProperty (docPath , PathItem ::getExtensions , path , PathItem ::setExtensions );
66+ // Operation
67+ mergeOperation (
68+ Router .GET , pattern , docPath .getGet (), path .getGet (), mergePolicy , path ::setGet );
69+ mergeOperation (
70+ Router .POST , pattern , docPath .getPost (), path .getPost (), mergePolicy , path ::setPost );
71+ mergeOperation (
72+ Router .PUT , pattern , docPath .getPut (), path .getPut (), mergePolicy , path ::setPut );
73+ mergeOperation (
74+ Router .PATCH ,
75+ pattern ,
76+ docPath .getPatch (),
77+ path .getPatch (),
78+ mergePolicy ,
79+ path ::setPatch );
80+ mergeOperation (
81+ Router .DELETE ,
82+ pattern ,
83+ docPath .getDelete (),
84+ path .getDelete (),
85+ mergePolicy ,
86+ path ::setDelete );
87+ mergeOperation (
88+ Router .HEAD , pattern , docPath .getHead (), path .getHead (), mergePolicy , path ::setHead );
89+ mergeOperation (
90+ Router .OPTIONS ,
91+ pattern ,
92+ docPath .getOptions (),
93+ path .getOptions (),
94+ mergePolicy ,
95+ path ::setOptions );
96+ mergeOperation (
97+ Router .TRACE ,
98+ pattern ,
99+ docPath .getTrace (),
100+ path .getTrace (),
101+ mergePolicy ,
102+ path ::setTrace );
103+ } else if (mergePolicy .handle ("Unknown path: \" " + pattern + "\" " )) {
104+ var newOperation = e .getValue ();
105+ clearMergePolicy (newOperation , PathItem ::getExtensions , PathItem ::setExtensions );
106+ clearMergePolicy (newOperation .getGet (), Operation ::getExtensions , Operation ::setExtensions );
107+ clearMergePolicy (
108+ newOperation .getPost (), Operation ::getExtensions , Operation ::setExtensions );
109+ clearMergePolicy (newOperation .getPut (), Operation ::getExtensions , Operation ::setExtensions );
110+ clearMergePolicy (
111+ newOperation .getPatch (), Operation ::getExtensions , Operation ::setExtensions );
112+ clearMergePolicy (
113+ newOperation .getDelete (), Operation ::getExtensions , Operation ::setExtensions );
114+ clearMergePolicy (
115+ newOperation .getHead (), Operation ::getExtensions , Operation ::setExtensions );
116+ clearMergePolicy (
117+ newOperation .getOptions (), Operation ::getExtensions , Operation ::setExtensions );
118+ clearMergePolicy (
119+ newOperation .getTrace (), Operation ::getExtensions , Operation ::setExtensions );
120+ paths .put (e .getKey (), newOperation );
121+ }
122+ }
123+ return paths ;
124+ }
125+
126+ private <T > void clearMergePolicy (
127+ T src , Function <T , Map <String , Object >> getter , BiConsumer <T , Map <String , Object >> setter ) {
128+ if (src != null ) {
129+ Map <String , Object > extensions = getter .apply (src );
130+ if (extensions != null ) {
131+ extensions .remove ("x-merge-policy" );
132+ if (extensions .isEmpty ()) {
133+ extensions = null ;
134+ }
135+ setter .accept (src , extensions );
136+ }
137+ }
138+ }
139+
140+ private void mergeOperation (
141+ String method ,
142+ String pattern ,
143+ Operation src ,
144+ Operation target ,
145+ MergePolicy defaultMergePolicy ,
146+ Consumer <Operation > appender ) {
147+ if (src != null ) {
148+ MergePolicy mergePolicy = MergePolicy .parse (src .getExtensions (), defaultMergePolicy );
149+ if (target != null ) {
150+ setProperty (src , Operation ::getTags , target , Operation ::setTags );
151+ setProperty (src , Operation ::getSummary , target , Operation ::setSummary );
152+ setProperty (src , Operation ::getDescription , target , Operation ::setDescription );
153+ setProperty (src , Operation ::getExternalDocs , target , Operation ::setExternalDocs );
154+ setProperty (src , Operation ::getOperationId , target , Operation ::setOperationId );
155+ setProperty (src , Operation ::getRequestBody , target , Operation ::setRequestBody );
156+ setProperty (src , Operation ::getResponses , target , Operation ::setResponses );
157+ setProperty (src , Operation ::getCallbacks , target , Operation ::setCallbacks );
158+ setProperty (src , Operation ::getDeprecated , target , Operation ::setDeprecated );
159+ setProperty (src , Operation ::getSecurity , target , Operation ::setSecurity );
160+ setProperty (src , Operation ::getServers , target , Operation ::setServers );
161+ setProperty (src , Operation ::getExtensions , target , Operation ::setExtensions );
162+
163+ // Parameter are sync in next line:
164+ // setProperty(src, Operation::getParameters, target, Operation::setParameters);
165+ var srcParameters =
166+ Optional .ofNullable (src .getParameters ()).orElseGet (List ::of ).stream ()
167+ .filter (Objects ::nonNull )
168+ .toList ();
169+ var targetParameters =
170+ Optional .ofNullable (target .getParameters ()).orElseGet (List ::of ).stream ()
171+ .filter (Objects ::nonNull )
172+ .toList ();
173+ for (var srcParameter : srcParameters ) {
174+ targetParameters .stream ()
175+ .filter (it -> it .getName ().equals (srcParameter .getName ()))
176+ .findFirst ()
177+ .ifPresent (targetParameter -> mergeParameter (srcParameter , targetParameter ));
178+ }
179+ } else if (mergePolicy .handle ("Operation not found: " + method + " " + pattern )) {
180+ appender .accept (src );
181+ }
182+ }
183+ }
184+
185+ private void mergeParameter (Parameter src , Parameter target ) {
186+ setProperty (src , Parameter ::getIn , target , Parameter ::setIn );
187+ setProperty (src , Parameter ::getDescription , target , Parameter ::setDescription );
188+ setProperty (src , Parameter ::getRequired , target , Parameter ::setRequired );
189+ setProperty (src , Parameter ::getDeprecated , target , Parameter ::setDeprecated );
190+ setProperty (src , Parameter ::getAllowEmptyValue , target , Parameter ::setAllowEmptyValue );
191+ setProperty (src , Parameter ::get$ref , target , Parameter ::set$ref );
192+ setProperty (src , Parameter ::getStyle , target , Parameter ::setStyle );
193+ setProperty (src , Parameter ::getExplode , target , Parameter ::setExplode );
194+ setProperty (src , Parameter ::getAllowReserved , target , Parameter ::setAllowReserved );
195+ setProperty (src , Parameter ::getSchema , target , Parameter ::setSchema );
196+ setProperty (src , Parameter ::getExamples , target , Parameter ::setExamples );
197+ setProperty (src , Parameter ::getExample , target , Parameter ::setExample );
198+ setProperty (src , Parameter ::getContent , target , Parameter ::setContent );
199+ setProperty (src , Parameter ::getExtensions , target , Parameter ::setExtensions );
200+ }
201+
202+ private <S , V > void setProperty (S src , Function <S , V > getter , S target , BiConsumer <S , V > setter ) {
203+ var value = getter .apply (src );
204+ // Copy only non-null values
205+ if (value != null ) {
206+ if (value instanceof Collection <?> collection ) {
207+ // non-empty
208+ if (!collection .isEmpty ()) {
209+ setter .accept (target , value );
210+ }
211+ } else if (value instanceof Map <?, ?> map ) {
212+ // non-empty
213+ if (!map .isEmpty ()) {
214+ setter .accept (target , value );
215+ }
216+ } else if (value instanceof CharSequence string ) {
217+ // non-empty
218+ if (!string .isEmpty ()) {
219+ setter .accept (target , value );
220+ }
221+ } else {
222+ setter .accept (target , value );
223+ }
224+ }
225+ }
58226}
0 commit comments