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 " +
@@ -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 ();
@@ -95,7 +100,11 @@ public ScalaSttp4JsoniterClientCodegen() {
95100 apiTemplateFiles .put ("api.mustache" , ".scala" );
96101 embeddedTemplateDir = templateDir = "scala-sttp4-jsoniter" ;
97102
98- String jsonValueClass = "io.circe.Json" ;
103+ // Scala 3 reserved words
104+ reservedWords .addAll (Arrays .asList ("enum" , "export" , "given" , "then" , "using" , "Request" , "Method" , "Either" ));
105+
106+ importMapping .put (jsonValueClass , jsonAstCodecImport );
107+ importMapping .put ("BigDecimal" , "scala.math.BigDecimal" );
99108
100109 additionalProperties .put (CodegenConstants .GROUP_ID , groupId );
101110 additionalProperties .put (CodegenConstants .ARTIFACT_ID , artifactId );
@@ -110,11 +119,6 @@ public ScalaSttp4JsoniterClientCodegen() {
110119 additionalProperties .put ("fnCodecName" , new CodecNameLambda ());
111120 additionalProperties .put ("fnHandleDownload" , new HandleDownloadLambda ());
112121
113- // importMapping.remove("Seq");
114- // importMapping.remove("List");
115- // importMapping.remove("Set");
116- // importMapping.remove("Map");
117-
118122 // TODO: there is no specific sttp mapping. All Scala Type mappings should be in
119123 // AbstractScala
120124 typeMapping = new HashMap <>();
@@ -130,17 +134,22 @@ public ScalaSttp4JsoniterClientCodegen() {
130134 typeMapping .put ("short" , "Short" );
131135 typeMapping .put ("char" , "Char" );
132136 typeMapping .put ("double" , "Double" );
133- typeMapping .put ("object" , jsonValueClass );
134137 typeMapping .put ("file" , "File" );
135138 typeMapping .put ("binary" , "File" );
136139 typeMapping .put ("number" , "Double" );
137140 typeMapping .put ("decimal" , "BigDecimal" );
138141 typeMapping .put ("ByteArray" , "Array[Byte]" );
142+
143+ // actually, these two *are* jsoniter+circe AST specific
144+ typeMapping .put ("object" , jsonValueClass );
139145 typeMapping .put ("AnyType" , jsonValueClass );
140146
141147 instantiationTypes .put ("array" , "ListBuffer" );
142148 instantiationTypes .put ("map" , "Map" );
143149
150+ // remove DATE_LIBRARY option, we don't need it
151+ cliOptions .removeIf (option -> option .getOpt ().equals (DATE_LIBRARY ));
152+
144153 properties .stream ()
145154 .map (Property ::toCliOptions )
146155 .flatMap (Collection ::stream )
@@ -161,6 +170,8 @@ public void processOpts() {
161170 supportingFiles .add (new SupportingFile ("jsonSupport.mustache" , invokerFolder , "JsonSupport.scala" ));
162171 supportingFiles .add (new SupportingFile ("additionalTypeSerializers.mustache" , invokerFolder ,
163172 "AdditionalTypeSerializers.scala" ));
173+ supportingFiles .add (new SupportingFile ("helpers.mustache" , invokerFolder ,
174+ "Helpers.scala" ));
164175 supportingFiles .add (new SupportingFile ("project/build.properties.mustache" , "project" , "build.properties" ));
165176 }
166177
@@ -188,55 +199,13 @@ public String encodePath(String input) {
188199 return buf .toString ();
189200 }
190201
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-
207202 @ Override
208203 public CodegenOperation fromOperation (String path ,
209204 String httpMethod ,
210205 Operation operation ,
211206 List <Server > servers ) {
212207 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-
208+ op .path = encodePath (path );
240209 return op ;
241210 }
242211
@@ -280,7 +249,7 @@ public String toApiName(String name) {
280249 if (!uniqueApiNames .contains (lowerCasedNextGeneratedApiName )) {
281250 uniqueApiNames .add (lowerCasedNextGeneratedApiName );
282251 apiNameMappings .put (name , nextGeneratedApiName );
283-
252+
284253 return nextGeneratedApiName ;
285254 }
286255 i ++;
@@ -338,13 +307,32 @@ private void postProcessUpdateImports(final Map<String, ModelsMap> models) {
338307 continue ;
339308 }
340309 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" );
310+
311+ boolean foundJsonImport = false ;
312+
313+ for (Map <String , String > anImport : imports ) {
314+ String importPath = anImport .get ("import" );
315+
344316 Map <String , String > item = new HashMap <>();
317+
318+ // remove any imports for io.circe.Json, it's a FQCN
319+ // but on the first encounter, add the import for the
320+ // jsoniter-scala circe AST codec as it will be necessary
321+ // for all places where io.circe.Json is used as request body
322+ // or response body
323+ if (importPath .contains (jsonValueClass )) {
324+ if (!foundJsonImport ) {
325+ foundJsonImport = true ;
326+ item .put ("import" , jsonAstCodecImport );
327+ newImports .add (item );
328+ }
329+
330+ continue ;
331+ }
332+
345333 if (importPath .startsWith (prefix )) {
346334 if (isEnumClass (importPath , enumRefs )) {
347- item .put ("import" , importPath .concat ("._ " ));
335+ item .put ("import" , importPath .concat (".* " ));
348336 newImports .add (item );
349337 }
350338 } else {
@@ -361,7 +349,7 @@ private Map<String, ModelsMap> getEnumRefs(final Map<String, ModelsMap> models)
361349 Map <String , ModelsMap > enums = new HashMap <>();
362350 for (String key : models .keySet ()) {
363351 CodegenModel model = ModelUtils .getModelByName (key , models );
364- if (model .isEnum ) {
352+ if (model != null && model .isEnum ) {
365353 ModelsMap objs = models .get (key );
366354 enums .put (key , objs );
367355 }
@@ -438,18 +426,18 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
438426 List <Map <String , String >> newImports = new ArrayList <>();
439427 List <Map <String , String >> imports = objs .getImports ();
440428 if (imports != null && !imports .isEmpty ()) {
441- Iterator <Map <String , String >> iterator = imports .iterator ();
442- while (iterator .hasNext ()) {
443- String importPath = iterator .next ().get ("import" );
429+ for (Map <String , String > anImport : imports ) {
430+ String importPath = anImport .get ("import" );
444431 Map <String , String > item = new HashMap <>();
445432 if (isEnumClass (importPath , enumRefs )) {
446- item .put ("import" , importPath .concat ("._ " ));
433+ item .put ("import" , importPath .concat (".* " ));
447434 } else {
448435 item .put ("import" , importPath );
449436 }
450437 newImports .add (item );
451438 }
452439 }
440+
453441 objs .setImports (newImports );
454442
455443 return super .postProcessOperationsWithModels (objs , allModels );
@@ -484,8 +472,14 @@ public String toParamName(String name) {
484472 public String toEnumName (CodegenProperty property ) {
485473 String identifier = formatIdentifier (property .baseName , true );
486474
487- // remove backticks because there are no capitalized reserved words in Scala
488475 if (identifier .startsWith ("`" ) && identifier .endsWith ("`" )) {
476+ // is it numeric?
477+ String unescaped = identifier .substring (1 , identifier .length () - 1 );
478+ if (StringUtils .isNumeric (unescaped )) {
479+ return identifier ; // keep backticks
480+ }
481+
482+ // remove backticks because there are no capitalized reserved words in Scala
489483 return identifier .substring (1 , identifier .length () - 1 );
490484 } else {
491485 return identifier ;
@@ -705,15 +699,4 @@ public String formatFragment(String fragment) {
705699 }
706700 }
707701
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-
719702}
0 commit comments