@@ -34,6 +34,7 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig {
34
34
protected String packageName ;
35
35
protected String packageVersion ;
36
36
protected String externCrateName ;
37
+ protected Map <String , Map <String , String >> pathSetMap = new HashMap <String , Map <String , String >>();
37
38
38
39
public RustServerCodegen () {
39
40
super ();
@@ -155,8 +156,9 @@ public RustServerCodegen() {
155
156
supportingFiles .add (new SupportingFile ("gitignore" , "" , ".gitignore" ));
156
157
supportingFiles .add (new SupportingFile ("lib.mustache" , "src" , "lib.rs" ));
157
158
supportingFiles .add (new SupportingFile ("models.mustache" , "src" , "models.rs" ));
158
- supportingFiles .add (new SupportingFile ("server.mustache" , "src" , "server.rs" ));
159
- supportingFiles .add (new SupportingFile ("client.mustache" , "src" , "client.rs" ));
159
+ supportingFiles .add (new SupportingFile ("server-mod.mustache" , "src/server" , "mod.rs" ));
160
+ supportingFiles .add (new SupportingFile ("server-auth.mustache" , "src/server" , "auth.rs" ));
161
+ supportingFiles .add (new SupportingFile ("client-mod.mustache" , "src/client" , "mod.rs" ));
160
162
supportingFiles .add (new SupportingFile ("mimetypes.mustache" , "src" , "mimetypes.rs" ));
161
163
supportingFiles .add (new SupportingFile ("example-server.mustache" , "examples" , "server.rs" ));
162
164
supportingFiles .add (new SupportingFile ("example-client.mustache" , "examples" , "client.rs" ));
@@ -448,75 +450,58 @@ boolean isMimetypePlainText(String mimetype) {
448
450
return mimetype .toLowerCase ().startsWith ("text/plain" );
449
451
}
450
452
453
+ boolean isMimetypeWwwFormUrlEncoded (String mimetype ) {
454
+ return mimetype .toLowerCase ().startsWith ("application/x-www-form-urlencoded" );
455
+ }
456
+
451
457
@ Override
452
458
public CodegenOperation fromOperation (String path , String httpMethod , Operation operation , Map <String , Model > definitions , Swagger swagger ) {
453
459
CodegenOperation op = super .fromOperation (path , httpMethod , operation , definitions , swagger );
460
+
461
+ // The Rust code will need to contain a series of regular expressions.
462
+ // For performance, we'll construct these at start-of-day and re-use
463
+ // them. That means we need labels for them.
464
+ //
465
+ // Construct a Rust constant (uppercase) token name, and ensure it's
466
+ // unique using a numeric tie-breaker if required.
467
+ String basePathId = sanitizeName (op .path .replace ("/" , "_" ).replace ("{" , "" ).replace ("}" , "" ).replaceAll ("^_" , "" )).toUpperCase ();
468
+ String pathId = basePathId ;
469
+ int pathIdTiebreaker = 2 ;
470
+ boolean found = false ;
471
+ while (pathSetMap .containsKey (pathId )) {
472
+ Map <String , String > pathSetEntry = pathSetMap .get (pathId );
473
+ if (pathSetEntry .get ("path" ).equals (op .path )) {
474
+ found = true ;
475
+ break ;
476
+ }
477
+ pathId = basePathId + pathIdTiebreaker ;
478
+ pathIdTiebreaker ++;
479
+ }
480
+
481
+ // Save off the regular expression and path details in the
482
+ // "pathSetMap", which we'll add to the source document that will be
483
+ // processed by the templates.
484
+ if (!found ) {
485
+ Map <String , String > pathSetEntry = new HashMap <String , String >();
486
+ pathSetEntry .put ("path" , op .path );
487
+ pathSetEntry .put ("PATH_ID" , pathId );
488
+ if (!op .pathParams .isEmpty ()) {
489
+ pathSetEntry .put ("hasPathParams" , "true" );
490
+ }
491
+ // Don't prefix with '^' so that the templates can put the
492
+ // basePath on the front.
493
+ pathSetEntry .put ("pathRegEx" , op .path .replace ("{" , "(?P<" ).replace ("}" , ">[^/?#]*)" ) + "$" );
494
+ pathSetMap .put (pathId , pathSetEntry );
495
+ }
496
+
454
497
op .vendorExtensions .put ("operation_id" , underscore (op .operationId ));
455
498
op .vendorExtensions .put ("uppercase_operation_id" , underscore (op .operationId ).toUpperCase ());
456
499
op .vendorExtensions .put ("path" , op .path .replace ("{" , ":" ).replace ("}" , "" ));
500
+ op .vendorExtensions .put ("PATH_ID" , pathId );
501
+ op .vendorExtensions .put ("hasPathParams" , !op .pathParams .isEmpty ());
457
502
op .vendorExtensions .put ("HttpMethod" , Character .toUpperCase (op .httpMethod .charAt (0 )) + op .httpMethod .substring (1 ).toLowerCase ());
458
- op .vendorExtensions .put ("httpmethod" , op .httpMethod .toLowerCase ());
459
503
for (CodegenParameter param : op .allParams ) {
460
- String example = null ;
461
-
462
- if (param .isString ) {
463
- if (param .dataFormat != null && param .dataFormat .equals ("byte" )) {
464
- param .vendorExtensions .put ("formatString" , "\\ \" {:?}\\ \" " );
465
- example = "swagger::ByteArray(\" " + ((param .example != null ) ? param .example : "" ) + "\" .to_string().into_bytes())" ;
466
- } else {
467
- param .vendorExtensions .put ("formatString" , "\\ \" {}\\ \" " );
468
- example = "\" " + ((param .example != null ) ? param .example : "" ) + "\" .to_string()" ;
469
- }
470
- } else if (param .isPrimitiveType ) {
471
- if ((param .isByteArray ) ||
472
- (param .isBinary )) {
473
- // Binary primitive types don't implement `Display`.
474
- param .vendorExtensions .put ("formatString" , "{:?}" );
475
- example = "swagger::ByteArray(Vec::from(\" " + ((param .example != null ) ? param .example : "" ) + "\" ))" ;
476
- } else {
477
- param .vendorExtensions .put ("formatString" , "{}" );
478
- example = (param .example != null ) ? param .example : "" ;
479
- }
480
- } else if (param .isListContainer ) {
481
- param .vendorExtensions .put ("formatString" , "{:?}" );
482
- example = (param .example != null ) ? param .example : "&Vec::new()" ;
483
- } else if (param .isFile ) {
484
- param .vendorExtensions .put ("formatString" , "{:?}" );
485
- op .vendorExtensions .put ("hasFile" , true );
486
- additionalProperties .put ("apiHasFile" , true );
487
- example = "Box::new(stream::once(Ok(b\" hello\" .to_vec()))) as Box<Stream<Item=_, Error=_> + Send>" ;
488
- } else {
489
- param .vendorExtensions .put ("formatString" , "{:?}" );
490
- if (param .example != null ) {
491
- example = "serde_json::from_str::<" + param .dataType + ">(\" " + param .example + "\" ).expect(\" Failed to parse JSON example\" )" ;
492
- }
493
- }
494
-
495
- if (param .required ) {
496
- if (example != null ) {
497
- param .vendorExtensions .put ("example" , example );
498
- } else if (param .isListContainer ) {
499
- // Use the empty list if we don't have an example
500
- param .vendorExtensions .put ("example" , "&Vec::new()" );
501
- }
502
- else {
503
- // If we don't have an example that we can provide, we need to disable the client example, as it won't build.
504
- param .vendorExtensions .put ("example" , "???" );
505
- op .vendorExtensions .put ("noClientExample" , Boolean .TRUE );
506
- }
507
- } else if ((param .dataFormat != null )&&((param .dataFormat .equals ("date-time" )) || (param .dataFormat .equals ("date" )))) {
508
- param .vendorExtensions .put ("formatString" , "{:?}" );
509
- param .vendorExtensions .put ("example" , "None" );
510
- } else {
511
- // Not required, so override the format string and example
512
- param .vendorExtensions .put ("formatString" , "{:?}" );
513
- if (param .isFile ) {
514
- // Optional file types are wrapped in a future
515
- param .vendorExtensions .put ("example" , (example != null ) ? "Box::new(future::ok(Some(" + example + "))) as Box<Future<Item=_, Error=_> + Send>" : "None" );
516
- } else {
517
- param .vendorExtensions .put ("example" , (example != null ) ? "Some(" + example + ")" : "None" );
518
- }
519
- }
504
+ processParam (param , op );
520
505
}
521
506
522
507
List <String > consumes = new ArrayList <String >();
@@ -544,6 +529,8 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
544
529
consumesXml = true ;
545
530
} else if (isMimetypePlainText (mimeType )) {
546
531
consumesPlainText = true ;
532
+ } else if (isMimetypeWwwFormUrlEncoded (mimeType )) {
533
+ additionalProperties .put ("usesUrlEncodedForm" , true );
547
534
}
548
535
549
536
mediaType .put ("mediaType" , mimeType );
@@ -587,7 +574,6 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
587
574
}
588
575
589
576
if (op .bodyParam != null ) {
590
-
591
577
if (paramHasXmlNamespace (op .bodyParam , definitions )){
592
578
op .bodyParam .vendorExtensions .put ("has_namespace" , "true" );
593
579
}
@@ -607,6 +593,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
607
593
608
594
}
609
595
for (CodegenParameter param : op .bodyParams ) {
596
+ processParam (param , op );
610
597
611
598
if (paramHasXmlNamespace (param , definitions )){
612
599
param .vendorExtensions .put ("has_namespace" , "true" );
@@ -624,9 +611,18 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
624
611
}
625
612
}
626
613
for (CodegenParameter param : op .headerParams ) {
614
+ // If a header uses UUIDs, we need to import the UUID package.
615
+ if (param .dataType .equals ("uuid::Uuid" )) {
616
+ additionalProperties .put ("apiUsesUuid" , true );
617
+ }
618
+ processParam (param , op );
619
+
627
620
// Give header params a name in camel case. CodegenParameters don't have a nameInCamelCase property.
628
621
param .vendorExtensions .put ("typeName" , toModelName (param .baseName ));
629
622
}
623
+ for (CodegenParameter param : op .formParams ) {
624
+ processParam (param , op );
625
+ }
630
626
for (CodegenResponse rsp : op .responses ) {
631
627
String [] words = rsp .message .split ("[^A-Za-z ]" );
632
628
String responseId ;
@@ -673,10 +669,16 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
673
669
}
674
670
}
675
671
for (CodegenProperty header : rsp .headers ) {
672
+ if (header .datatype .equals ("uuid::Uuid" )) {
673
+ additionalProperties .put ("apiUsesUuid" , true );
674
+ }
676
675
header .nameInCamelCase = toModelName (header .baseName );
677
676
}
678
677
}
679
678
for (CodegenProperty header : op .responseHeaders ) {
679
+ if (header .datatype .equals ("uuid::Uuid" )) {
680
+ additionalProperties .put ("apiUsesUuid" , true );
681
+ }
680
682
header .nameInCamelCase = toModelName (header .baseName );
681
683
}
682
684
@@ -860,6 +862,27 @@ public Map<String, Object> postProcessSupportingFileData(Map<String, Object> obj
860
862
LOGGER .error (e .getMessage (), e );
861
863
}
862
864
}
865
+
866
+ // We previously built a mapping from path to path ID and regular
867
+ // expression - see fromOperation for details. Sort it and add an
868
+ // index, and then add it to the objects that we're about to pass to
869
+ // the templates to process.
870
+ List <Map .Entry <String , Map <String , String >>> pathSetEntryList = new ArrayList (pathSetMap .entrySet ());
871
+ Collections .sort (pathSetEntryList , new Comparator <Map .Entry <String , Map <String , String >>>() {
872
+ public int compare (Map .Entry <String , Map <String , String >> a , Map .Entry <String , Map <String , String >> b ) {
873
+ return a .getValue ().get ("path" ).compareTo (b .getValue ().get ("path" ));
874
+ }
875
+ });
876
+ List pathSet = new ArrayList <Map <String , String >>();
877
+ int index = 0 ;
878
+ for (Map .Entry <String , Map <String , String >> pathSetEntry : pathSetEntryList ) {
879
+ Map <String , String > pathSetEntryValue = pathSetEntry .getValue ();
880
+ pathSetEntryValue .put ("index" , Integer .toString (index ));
881
+ index ++;
882
+ pathSet .add (pathSetEntryValue );
883
+ }
884
+ objs .put ("pathSet" , pathSet );
885
+
863
886
return super .postProcessSupportingFileData (objs );
864
887
}
865
888
@@ -1029,4 +1052,67 @@ private boolean paramHasXmlNamespace(CodegenParameter param, Map<String, Model>
1029
1052
}
1030
1053
return false ;
1031
1054
}
1055
+
1056
+ private void processParam (CodegenParameter param , CodegenOperation op ) {
1057
+ String example = null ;
1058
+
1059
+ if (param .isString ) {
1060
+ if (param .dataFormat != null && param .dataFormat .equals ("byte" )) {
1061
+ param .vendorExtensions .put ("formatString" , "\\ \" {:?}\\ \" " );
1062
+ example = "swagger::ByteArray(\" " + ((param .example != null ) ? param .example : "" ) + "\" .to_string().into_bytes())" ;
1063
+ } else {
1064
+ param .vendorExtensions .put ("formatString" , "\\ \" {}\\ \" " );
1065
+ example = "\" " + ((param .example != null ) ? param .example : "" ) + "\" .to_string()" ;
1066
+ }
1067
+ } else if (param .isPrimitiveType ) {
1068
+ if ((param .isByteArray ) ||
1069
+ (param .isBinary )) {
1070
+ // Binary primitive types don't implement `Display`.
1071
+ param .vendorExtensions .put ("formatString" , "{:?}" );
1072
+ example = "swagger::ByteArray(Vec::from(\" " + ((param .example != null ) ? param .example : "" ) + "\" ))" ;
1073
+ } else {
1074
+ param .vendorExtensions .put ("formatString" , "{}" );
1075
+ example = (param .example != null ) ? param .example : "" ;
1076
+ }
1077
+ } else if (param .isListContainer ) {
1078
+ param .vendorExtensions .put ("formatString" , "{:?}" );
1079
+ example = (param .example != null ) ? param .example : "&Vec::new()" ;
1080
+ } else if (param .isFile ) {
1081
+ param .vendorExtensions .put ("formatString" , "{:?}" );
1082
+ op .vendorExtensions .put ("hasFile" , true );
1083
+ additionalProperties .put ("apiHasFile" , true );
1084
+ example = "Box::new(stream::once(Ok(b\" hello\" .to_vec()))) as Box<Stream<Item=_, Error=_> + Send>" ;
1085
+ } else {
1086
+ param .vendorExtensions .put ("formatString" , "{:?}" );
1087
+ if (param .example != null ) {
1088
+ example = "serde_json::from_str::<" + param .dataType + ">(\" " + param .example + "\" ).expect(\" Failed to parse JSON example\" )" ;
1089
+ }
1090
+ }
1091
+
1092
+ if (param .required ) {
1093
+ if (example != null ) {
1094
+ param .vendorExtensions .put ("example" , example );
1095
+ } else if (param .isListContainer ) {
1096
+ // Use the empty list if we don't have an example
1097
+ param .vendorExtensions .put ("example" , "&Vec::new()" );
1098
+ }
1099
+ else {
1100
+ // If we don't have an example that we can provide, we need to disable the client example, as it won't build.
1101
+ param .vendorExtensions .put ("example" , "???" );
1102
+ op .vendorExtensions .put ("noClientExample" , Boolean .TRUE );
1103
+ }
1104
+ } else if ((param .dataFormat != null )&&((param .dataFormat .equals ("date-time" )) || (param .dataFormat .equals ("date" )))) {
1105
+ param .vendorExtensions .put ("formatString" , "{:?}" );
1106
+ param .vendorExtensions .put ("example" , "None" );
1107
+ } else {
1108
+ // Not required, so override the format string and example
1109
+ param .vendorExtensions .put ("formatString" , "{:?}" );
1110
+ if (param .isFile ) {
1111
+ // Optional file types are wrapped in a future
1112
+ param .vendorExtensions .put ("example" , (example != null ) ? "Box::new(future::ok(Some(" + example + "))) as Box<Future<Item=_, Error=_> + Send>" : "None" );
1113
+ } else {
1114
+ param .vendorExtensions .put ("example" , (example != null ) ? "Some(" + example + ")" : "None" );
1115
+ }
1116
+ }
1117
+ }
1032
1118
}
0 commit comments