44import io .swagger .v3 .oas .models .media .Schema ;
55import io .swagger .v3 .oas .models .security .SecurityScheme ;
66import io .swagger .v3 .oas .models .servers .Server ;
7- import lombok .Getter ;
87import org .apache .commons .lang3 .StringUtils ;
98import org .openapitools .codegen .*;
109import org .openapitools .codegen .meta .GeneratorMetadata ;
1817import org .slf4j .Logger ;
1918import org .slf4j .LoggerFactory ;
2019
20+ import static org .openapitools .codegen .languages .AbstractJavaCodegen .DATE_LIBRARY ;
21+
2122import java .io .File ;
2223import java .util .*;
2324import java .util .regex .Matcher ;
2425import java .util .regex .Pattern ;
2526
26- import static org .openapitools .codegen .utils .StringUtils .camelize ;
27-
2827public class ScalaSttp4JsoniterClientCodegen extends AbstractScalaCodegen implements CodegenConfig {
2928 private static final StringProperty STTP_CLIENT_VERSION = new StringProperty ("sttpClientVersion" ,
3029 "The version of " +
3130 "sttp client" ,
32- "4.0.0-M19 " );
31+ "4.0.0-RC1 " );
3332 private static final BooleanProperty USE_SEPARATE_ERROR_CHANNEL = new BooleanProperty ("separateErrorChannel" ,
3433 "Whether to return response as " +
3534 "F[Either[ResponseError[ErrorType], ReturnType]]] or to flatten " +
@@ -46,7 +45,13 @@ public class ScalaSttp4JsoniterClientCodegen extends AbstractScalaCodegen implem
4645 private static final List <Property <?>> properties = Arrays .asList (
4746 STTP_CLIENT_VERSION , USE_SEPARATE_ERROR_CHANNEL , JSONITER_VERSION , PACKAGE_PROPERTY );
4847
49- private static final Set <String > NO_JSON_CODEC_TYPES = new HashSet <>(Arrays .asList ("UUID" , "URI" , "URL" , "File" , "Path" ));
48+ private static final String jsonClassBaseName = "Json" ;
49+ private static final String jsonValueClass = "io.circe.Json" ;
50+ private static final String jsonAstCodecImport = "com.github.plokhotnyuk.jsoniter_scala.circe.JsoniterScalaCodec.*" ;
51+
52+ private static final Set <String > NO_JSON_CODEC_TYPES = new HashSet <>(Arrays .asList (
53+ "UUID" , "URI" , "URL" , "File" , "Path" , jsonClassBaseName , jsonValueClass , "BigDecimal"
54+ ));
5055
5156 private final Logger LOGGER = LoggerFactory .getLogger (ScalaSttp4JsoniterClientCodegen .class );
5257
@@ -61,8 +66,8 @@ public class ScalaSttp4JsoniterClientCodegen extends AbstractScalaCodegen implem
6166
6267 Map <String , ModelsMap > enumRefs = new HashMap <>();
6368
64- private Map <String , String > apiNameMappings = new HashMap <>();
65- private Set <String > uniqueApiNames = new HashSet <>();
69+ private final Map <String , String > apiNameMappings = new HashMap <>();
70+ private final Set <String > uniqueApiNames = new HashSet <>();
6671
6772 public ScalaSttp4JsoniterClientCodegen () {
6873 super ();
@@ -80,12 +85,9 @@ public ScalaSttp4JsoniterClientCodegen() {
8085 .excludeGlobalFeatures (
8186 GlobalFeature .XMLStructureDefinitions ,
8287 GlobalFeature .Callbacks ,
83- GlobalFeature .LinkObjects ,
84- GlobalFeature .ParameterStyling )
88+ GlobalFeature .LinkObjects )
8589 .excludeSchemaSupportFeatures (
8690 SchemaSupportFeature .Polymorphism )
87- .excludeParameterFeatures (
88- ParameterFeature .Cookie )
8991 .includeClientModificationFeatures (
9092 ClientModificationFeature .BasePath ,
9193 ClientModificationFeature .UserAgent ));
@@ -95,7 +97,11 @@ public ScalaSttp4JsoniterClientCodegen() {
9597 apiTemplateFiles .put ("api.mustache" , ".scala" );
9698 embeddedTemplateDir = templateDir = "scala-sttp4-jsoniter" ;
9799
98- String jsonValueClass = "io.circe.Json" ;
100+ // Scala 3 reserved words
101+ reservedWords .addAll (Arrays .asList ("enum" , "export" , "given" , "then" , "using" , "Request" , "Method" , "Either" ));
102+
103+ importMapping .put (jsonValueClass , jsonAstCodecImport );
104+ importMapping .put ("BigDecimal" , "scala.math.BigDecimal" );
99105
100106 additionalProperties .put (CodegenConstants .GROUP_ID , groupId );
101107 additionalProperties .put (CodegenConstants .ARTIFACT_ID , artifactId );
@@ -109,11 +115,7 @@ public ScalaSttp4JsoniterClientCodegen() {
109115 additionalProperties .put ("fnEnumEntry" , new EnumEntryLambda ());
110116 additionalProperties .put ("fnCodecName" , new CodecNameLambda ());
111117 additionalProperties .put ("fnHandleDownload" , new HandleDownloadLambda ());
112-
113- // importMapping.remove("Seq");
114- // importMapping.remove("List");
115- // importMapping.remove("Set");
116- // importMapping.remove("Map");
118+ additionalProperties .put ("fnEnumLeaf" , new EnumLeafLambda ());
117119
118120 // TODO: there is no specific sttp mapping. All Scala Type mappings should be in
119121 // AbstractScala
@@ -130,17 +132,22 @@ public ScalaSttp4JsoniterClientCodegen() {
130132 typeMapping .put ("short" , "Short" );
131133 typeMapping .put ("char" , "Char" );
132134 typeMapping .put ("double" , "Double" );
133- typeMapping .put ("object" , jsonValueClass );
134135 typeMapping .put ("file" , "File" );
135136 typeMapping .put ("binary" , "File" );
136137 typeMapping .put ("number" , "Double" );
137138 typeMapping .put ("decimal" , "BigDecimal" );
138139 typeMapping .put ("ByteArray" , "Array[Byte]" );
140+
141+ // actually, these two *are* jsoniter+circe AST specific
142+ typeMapping .put ("object" , jsonValueClass );
139143 typeMapping .put ("AnyType" , jsonValueClass );
140144
141145 instantiationTypes .put ("array" , "ListBuffer" );
142146 instantiationTypes .put ("map" , "Map" );
143147
148+ // remove DATE_LIBRARY option, we don't need it
149+ cliOptions .removeIf (option -> option .getOpt ().equals (DATE_LIBRARY ));
150+
144151 properties .stream ()
145152 .map (Property ::toCliOptions )
146153 .flatMap (Collection ::stream )
@@ -161,6 +168,8 @@ public void processOpts() {
161168 supportingFiles .add (new SupportingFile ("jsonSupport.mustache" , invokerFolder , "JsonSupport.scala" ));
162169 supportingFiles .add (new SupportingFile ("additionalTypeSerializers.mustache" , invokerFolder ,
163170 "AdditionalTypeSerializers.scala" ));
171+ supportingFiles .add (new SupportingFile ("helpers.mustache" , invokerFolder ,
172+ "Helpers.scala" ));
164173 supportingFiles .add (new SupportingFile ("project/build.properties.mustache" , "project" , "build.properties" ));
165174 }
166175
@@ -182,61 +191,19 @@ public String encodePath(String input) {
182191 StringBuffer buf = new StringBuffer (path .length ());
183192 Matcher matcher = Pattern .compile ("[{](.*?)[}]" ).matcher (path );
184193 while (matcher .find ()) {
185- matcher .appendReplacement (buf , "\\ ${" + toParamName (matcher .group (0 )) + "}" );
194+ matcher .appendReplacement (buf , "\\ ${" + toParamName (matcher .group (0 )). replace ( "`" , "" ) + "PathParam }" );
186195 }
187196 matcher .appendTail (buf );
188197 return buf .toString ();
189198 }
190199
191- private PathMetadata parseAndEncodePath (String input ) {
192- String path = super .encodePath (input );
193- ArrayList <String > pathParams = new ArrayList <>();
194-
195- // The parameter names in the URI must be converted to the same case as
196- // the method parameter.
197- StringBuffer buf = new StringBuffer (path .length ());
198- Matcher matcher = Pattern .compile ("[{](.*?)[}]" ).matcher (path );
199- while (matcher .find ()) {
200- matcher .appendReplacement (buf , "\\ ${" + toParamName (matcher .group (0 )) + "}" );
201- pathParams .add (matcher .group (0 ));
202- }
203- matcher .appendTail (buf );
204- return new PathMetadata (buf .toString (), pathParams );
205- }
206-
207200 @ Override
208201 public CodegenOperation fromOperation (String path ,
209202 String httpMethod ,
210203 Operation operation ,
211204 List <Server > servers ) {
212205 CodegenOperation op = super .fromOperation (path , httpMethod , operation , servers );
213-
214- PathMetadata pathMetadata = parseAndEncodePath (path );
215-
216- op .path = pathMetadata .getPath ();
217-
218- for (String pathParam : pathMetadata .getPathParams ()) {
219- CodegenParameter param = new CodegenParameter ();
220- param .isPathParam = true ;
221- param .baseName = pathParam ;
222- param .paramName = toParamName (pathParam );
223- param .dataType = "String" ;
224- param .required = true ;
225-
226- boolean alreadyExists = false ;
227- for (CodegenParameter existingParam : op .pathParams ) {
228- if (existingParam .baseName .equals (param .baseName ) || existingParam .paramName .equals (param .paramName )) {
229- alreadyExists = true ;
230- break ;
231- }
232- }
233-
234- if (!alreadyExists ) {
235- op .pathParams .add (param );
236- op .allParams .add (param );
237- }
238- }
239-
206+ op .path = encodePath (path );
240207 return op ;
241208 }
242209
@@ -280,7 +247,7 @@ public String toApiName(String name) {
280247 if (!uniqueApiNames .contains (lowerCasedNextGeneratedApiName )) {
281248 uniqueApiNames .add (lowerCasedNextGeneratedApiName );
282249 apiNameMappings .put (name , nextGeneratedApiName );
283-
250+
284251 return nextGeneratedApiName ;
285252 }
286253 i ++;
@@ -338,13 +305,32 @@ private void postProcessUpdateImports(final Map<String, ModelsMap> models) {
338305 continue ;
339306 }
340307 List <Map <String , String >> newImports = new ArrayList <>();
341- Iterator <Map <String , String >> iterator = imports .iterator ();
342- while (iterator .hasNext ()) {
343- String importPath = iterator .next ().get ("import" );
308+
309+ boolean foundJsonImport = false ;
310+
311+ for (Map <String , String > anImport : imports ) {
312+ String importPath = anImport .get ("import" );
313+
344314 Map <String , String > item = new HashMap <>();
315+
316+ // remove any imports for io.circe.Json, it's a FQCN
317+ // but on the first encounter, add the import for the
318+ // jsoniter-scala circe AST codec as it will be necessary
319+ // for all places where io.circe.Json is used as request body
320+ // or response body
321+ if (importPath .contains (jsonValueClass )) {
322+ if (!foundJsonImport ) {
323+ foundJsonImport = true ;
324+ item .put ("import" , jsonAstCodecImport );
325+ newImports .add (item );
326+ }
327+
328+ continue ;
329+ }
330+
345331 if (importPath .startsWith (prefix )) {
346332 if (isEnumClass (importPath , enumRefs )) {
347- item .put ("import" , importPath .concat ("._ " ));
333+ item .put ("import" , importPath .concat (".* " ));
348334 newImports .add (item );
349335 }
350336 } else {
@@ -361,7 +347,7 @@ private Map<String, ModelsMap> getEnumRefs(final Map<String, ModelsMap> models)
361347 Map <String , ModelsMap > enums = new HashMap <>();
362348 for (String key : models .keySet ()) {
363349 CodegenModel model = ModelUtils .getModelByName (key , models );
364- if (model .isEnum ) {
350+ if (model != null && model .isEnum ) {
365351 ModelsMap objs = models .get (key );
366352 enums .put (key , objs );
367353 }
@@ -438,18 +424,22 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
438424 List <Map <String , String >> newImports = new ArrayList <>();
439425 List <Map <String , String >> imports = objs .getImports ();
440426 if (imports != null && !imports .isEmpty ()) {
441- Iterator <Map <String , String >> iterator = imports .iterator ();
442- while (iterator .hasNext ()) {
443- String importPath = iterator .next ().get ("import" );
427+ for (Map <String , String > anImport : imports ) {
428+ String importPath = anImport .get ("import" );
444429 Map <String , String > item = new HashMap <>();
445430 if (isEnumClass (importPath , enumRefs )) {
446- item .put ("import" , importPath .concat ("._" ));
431+ item .put ("import" , importPath .concat (".*" ));
432+ Map <String , String > enumClassImport = new HashMap <>();
433+ enumClassImport .put ("import" , importPath );
434+ newImports .add (item );
435+ newImports .add (enumClassImport );
447436 } else {
448437 item .put ("import" , importPath );
438+ newImports .add (item );
449439 }
450- newImports .add (item );
451440 }
452441 }
442+
453443 objs .setImports (newImports );
454444
455445 return super .postProcessOperationsWithModels (objs , allModels );
@@ -484,8 +474,14 @@ public String toParamName(String name) {
484474 public String toEnumName (CodegenProperty property ) {
485475 String identifier = formatIdentifier (property .baseName , true );
486476
487- // remove backticks because there are no capitalized reserved words in Scala
488477 if (identifier .startsWith ("`" ) && identifier .endsWith ("`" )) {
478+ // is it numeric?
479+ String unescaped = identifier .substring (1 , identifier .length () - 1 );
480+ if (StringUtils .isNumeric (unescaped )) {
481+ return identifier ; // keep backticks
482+ }
483+
484+ // remove backticks because there are no capitalized reserved words in Scala
489485 return identifier .substring (1 , identifier .length () - 1 );
490486 } else {
491487 return identifier ;
@@ -676,13 +672,23 @@ public String formatFragment(String fragment) {
676672 }
677673 }
678674
679- private class EnumEntryLambda extends CustomLambda {
675+ private static class EnumEntryLambda extends CustomLambda {
676+ @ Override
677+ public String formatFragment (String fragment ) {
678+ if (fragment .isBlank ()) {
679+ return "NotPresent" ;
680+ }
681+ return "`" + fragment + "`" ;
682+ }
683+ }
684+
685+ private static class EnumLeafLambda extends CustomLambda {
680686 @ Override
681687 public String formatFragment (String fragment ) {
682688 if (fragment .isBlank ()) {
683689 return "NotPresent" ;
684690 }
685- return formatIdentifier ( fragment , true );
691+ return fragment . replace ( "`" , "" );
686692 }
687693 }
688694
@@ -698,22 +704,11 @@ private static class HandleDownloadLambda extends CustomLambda {
698704 @ Override
699705 public String formatFragment (String fragment ) {
700706 if (fragment .equals ("asJson[File]" )) {
701- return "asFile(File.createTempFile(\" download\" , \" .tmp\" )).mapLeft( errStr => DeserializationException(errStr, new Exception(errStr)))" ;
707+ return "asFile(File.createTempFile(\" download\" , \" .tmp\" )).mapWithMetadata((result, metadata) => result.left.map( errStr => ResponseException. DeserializationException(errStr, new Exception(errStr), metadata )))" ;
702708 } else {
703709 return fragment ;
704710 }
705711 }
706712 }
707713
708- @ Getter
709- private static class PathMetadata {
710- private final String path ;
711- private final ArrayList <String > pathParams ;
712-
713- PathMetadata (String path , ArrayList <String > pathParams ) {
714- this .path = path ;
715- this .pathParams = pathParams ;
716- }
717- }
718-
719714}
0 commit comments