18
18
19
19
import io .swagger .v3 .oas .models .OpenAPI ;
20
20
import io .swagger .v3 .oas .models .media .Schema ;
21
+ import io .swagger .v3 .oas .models .parameters .RequestBody ;
21
22
import org .apache .commons .io .FileUtils ;
22
23
import org .openapitools .codegen .*;
23
24
import org .openapitools .codegen .model .ModelMap ;
@@ -394,6 +395,12 @@ public void processOpenAPI(OpenAPI openAPI) {
394
395
}
395
396
396
397
398
+ /**
399
+ * This class is used in pathExtractorParams.mustache.
400
+ *
401
+ * It exposes some methods which make it more readable
402
+ * for that mustache snippet, and also isolates the logic needed for the path extractors
403
+ */
397
404
public static class ParamPart {
398
405
final CodegenParameter param ;
399
406
final String name ;
@@ -416,7 +423,9 @@ public ParamPart(String name, CodegenParameter param) {
416
423
}
417
424
418
425
/**
419
- * Cask will compile but 'initialize' can throw a route overlap exception:
426
+ * This data structure is here to manually identify and fix routes which will overlap (e.g. GET /foo/bar and GET /foo/bazz)
427
+ *
428
+ * If we added these as individual routes, then Cask itself will compile, but calling 'initialize' throws a route overlap exception:
420
429
* <p>
421
430
* {{{
422
431
* Routes overlap with wildcards: get /user/logout, get /user/:username, get /user/login
@@ -672,9 +681,12 @@ private void postProcessModel(CodegenModel model) {
672
681
673
682
model .getVars ().forEach (this ::postProcessProperty );
674
683
model .getAllVars ().forEach (this ::postProcessProperty );
684
+
685
+
686
+ model .vendorExtensions .put ("x-has-one-of" , model .oneOf != null && !model .oneOf .isEmpty ());
675
687
}
676
688
677
- private static void postProcessOperation (CodegenOperation op ) {
689
+ private static void postProcessOperation (final CodegenOperation op ) {
678
690
// force http method to lower case
679
691
op .httpMethod = op .httpMethod .toLowerCase (Locale .ROOT );
680
692
@@ -710,9 +722,33 @@ private static void postProcessOperation(CodegenOperation op) {
710
722
.collect (Collectors .toCollection (LinkedHashSet ::new ));
711
723
712
724
var responseType = responses .isEmpty () ? "Unit" : String .join (" | " , responses );
725
+ op .vendorExtensions .put ("x-import-response-implicits" , importResponseImplicits (op ));
713
726
op .vendorExtensions .put ("x-response-type" , responseType );
714
727
}
715
728
729
+ /**
730
+ * We need to bring the response type into scope in order to use the upickle implicits
731
+ * only if the response type has a 'oneOf' type, which means it's a union type with a
732
+ * companion object containing the ReadWriter
733
+ *
734
+ * @param op
735
+ * @return true if we need to provide an import
736
+ */
737
+ private static boolean importResponseImplicits (final CodegenOperation op ) {
738
+ final Set <String > importBlacklist = Set .of ("File" );
739
+
740
+ boolean doImport = false ;
741
+ for (var response : op .responses ) {
742
+ // we should ignore generic types like Seq[...] or Map[..] types
743
+ var isPolymorphic = response .dataType != null && response .dataType .contains ("[" );
744
+ if (response .isModel && !importBlacklist .contains (response .dataType ) && !isPolymorphic ) {
745
+ doImport = true ;
746
+ break ;
747
+ }
748
+ }
749
+ return doImport ;
750
+ }
751
+
716
752
/**
717
753
* primitive or enum types don't have Data representations
718
754
* @param p the property
@@ -747,6 +783,10 @@ private static boolean isByteArray(final CodegenProperty p) {
747
783
return "byte" .equalsIgnoreCase (p .dataFormat ); // &&
748
784
}
749
785
786
+ private static boolean wrapInOptional (CodegenProperty p ) {
787
+ return !p .required && !p .isArray && !p .isMap ;
788
+ }
789
+
750
790
/**
751
791
* this parameter is used to create the function:
752
792
* {{{
@@ -761,19 +801,18 @@ private static boolean isByteArray(final CodegenProperty p) {
761
801
* and then back again
762
802
*/
763
803
private static String asDataCode (final CodegenProperty p , final Set <String > typesWhichDoNotNeedMapping ) {
764
- final var wrapInOptional = !p .required && !p .isArray && !p .isMap ;
765
804
String code = "" ;
766
805
767
806
String dv = defaultValueNonOption (p , p .defaultValue );
768
807
769
808
if (doesNotNeedMapping (p , typesWhichDoNotNeedMapping )) {
770
- if (wrapInOptional ) {
809
+ if (wrapInOptional ( p ) ) {
771
810
code = String .format (Locale .ROOT , "%s.getOrElse(%s) /* 1 */" , p .name , dv );
772
811
} else {
773
812
code = String .format (Locale .ROOT , "%s /* 2 */" , p .name );
774
813
}
775
814
} else {
776
- if (wrapInOptional ) {
815
+ if (wrapInOptional ( p ) ) {
777
816
if (isByteArray (p )) {
778
817
code = String .format (Locale .ROOT , "%s.getOrElse(%s) /* 3 */" , p .name , dv );
779
818
} else {
@@ -782,11 +821,15 @@ private static String asDataCode(final CodegenProperty p, final Set<String> type
782
821
} else if (p .isArray ) {
783
822
if (isByteArray (p )) {
784
823
code = String .format (Locale .ROOT , "%s /* 5 */" , p .name );
824
+ } else if (!isObjectArray (p )) {
825
+ code = String .format (Locale .ROOT , "%s /* 5.1 */" , p .name );
785
826
} else {
786
827
code = String .format (Locale .ROOT , "%s.map(_.asData) /* 6 */" , p .name );
787
828
}
829
+ } else if (p .isMap ) {
830
+ code = String .format (Locale .ROOT , "%s /* 7 */" , p .name );
788
831
} else {
789
- code = String .format (Locale .ROOT , "%s.asData /* 7 */" , p .name );
832
+ code = String .format (Locale .ROOT , "%s.asData /* 8 */" , p .name );
790
833
}
791
834
}
792
835
return code ;
@@ -807,24 +850,25 @@ private static String asDataCode(final CodegenProperty p, final Set<String> type
807
850
* @return
808
851
*/
809
852
private static String asModelCode (final CodegenProperty p , final Set <String > typesWhichDoNotNeedMapping ) {
810
- final var wrapInOptional = !p .required && !p .isArray && !p .isMap ;
811
853
String code = "" ;
812
854
813
855
if (doesNotNeedMapping (p , typesWhichDoNotNeedMapping )) {
814
- if (wrapInOptional ) {
856
+ if (wrapInOptional ( p ) ) {
815
857
code = String .format (Locale .ROOT , "Option(%s) /* 1 */" , p .name );
816
858
} else {
817
859
code = String .format (Locale .ROOT , "%s /* 2 */" , p .name );
818
860
}
819
861
} else {
820
- if (wrapInOptional ) {
862
+ if (wrapInOptional ( p ) ) {
821
863
if (isByteArray (p )) {
822
864
code = String .format (Locale .ROOT , "Option(%s) /* 3 */" , p .name );
823
865
} else {
824
866
code = String .format (Locale .ROOT , "Option(%s).map(_.asModel) /* 4 */" , p .name );
825
867
}
826
868
} else if (p .isArray ) {
827
869
code = String .format (Locale .ROOT , "%s.map(_.asModel) /* 5 */" , p .name );
870
+ } else if (p .isMap ) {
871
+ code = String .format (Locale .ROOT , "%s /* 5.1 */" , p .name );
828
872
} else {
829
873
code = String .format (Locale .ROOT , "%s.asModel /* 6 */" , p .name );
830
874
}
@@ -863,8 +907,17 @@ private String ensureNonKeyword(String text) {
863
907
return text ;
864
908
}
865
909
910
+ private static boolean hasItemModel (final CodegenProperty p ) {
911
+ return p .items != null && p .items .isModel ;
912
+ }
913
+
914
+ private static boolean isObjectArray (final CodegenProperty p ) {
915
+ return p .isArray && hasItemModel (p );
916
+ }
917
+
866
918
private void postProcessProperty (final CodegenProperty p ) {
867
- p .vendorExtensions .put ("x-datatype-model" , asScalaDataType (p , p .required , false ));
919
+
920
+ p .vendorExtensions .put ("x-datatype-model" , asScalaDataType (p , p .required , false , wrapInOptional (p )));
868
921
p .vendorExtensions .put ("x-defaultValue-model" , defaultValue (p , p .required , p .defaultValue ));
869
922
final String dataTypeData = asScalaDataType (p , p .required , true );
870
923
p .vendorExtensions .put ("x-datatype-data" , dataTypeData );
@@ -878,7 +931,7 @@ private void postProcessProperty(final CodegenProperty p) {
878
931
p ._enum = p ._enum .stream ().map (this ::ensureNonKeyword ).collect (Collectors .toList ());
879
932
}
880
933
881
- /**
934
+ /*
882
935
* This is a fix for the enum property "type" declared like this:
883
936
* {{{
884
937
* type:
@@ -908,6 +961,9 @@ private void postProcessProperty(final CodegenProperty p) {
908
961
)).collect (Collectors .toSet ());
909
962
typesWhichShouldNotBeMapped .add ("byte" );
910
963
964
+ // when deserialising map objects, the logic is tricky.
965
+ p .vendorExtensions .put ("x-deserialize-asModelMap" , p .isMap && hasItemModel (p ));
966
+
911
967
// the 'asModel' logic for modelData.mustache
912
968
//
913
969
// if it's optional (not required), then wrap the value in Option()
@@ -916,16 +972,6 @@ private void postProcessProperty(final CodegenProperty p) {
916
972
p .vendorExtensions .put ("x-asData" , asDataCode (p , typesWhichShouldNotBeMapped ));
917
973
p .vendorExtensions .put ("x-asModel" , asModelCode (p , typesWhichShouldNotBeMapped ));
918
974
919
- // if it's an array or optional, we need to map it as a model -- unless it's a map,
920
- // in which case we have to map the values
921
- boolean hasItemModel = p .items != null && p .items .isModel ;
922
- boolean isObjectArray = p .isArray && hasItemModel ;
923
- boolean isOptionalObj = !p .required && p .isModel ;
924
- p .vendorExtensions .put ("x-map-asModel" , (isOptionalObj || isObjectArray ) && !p .isMap );
925
-
926
- // when deserialising map objects, the logic is tricky.
927
- p .vendorExtensions .put ("x-deserialize-asModelMap" , p .isMap && hasItemModel );
928
-
929
975
// for some reason, an openapi spec with pattern field like this:
930
976
// pattern: '^[A-Za-z]+$'
931
977
// will result in the pattern property text of
@@ -934,6 +980,20 @@ private void postProcessProperty(final CodegenProperty p) {
934
980
p .pattern = p .pattern .substring (1 , p .pattern .length () - 1 );
935
981
}
936
982
983
+ // in our model class definition laid out in modelClass.mustache, we use 'Option' for non-required
984
+ // properties only when they don't have a sensible 'empty' value (e.g. maps and lists).
985
+ //
986
+ // that is to say, we're trying to avoid having:
987
+ //
988
+ // someOptionalField : Option[Seq[Foo]]
989
+ //
990
+ // when we could just have e.g.
991
+ //
992
+ // someOptionalField : Seq[Foo]
993
+ //
994
+ // with an empty value
995
+ p .vendorExtensions .put ("x-model-needs-option" , wrapInOptional (p ));
996
+
937
997
}
938
998
939
999
0 commit comments