Skip to content

Commit 7970d4b

Browse files
authored
fix(rust-server): implement ValidateComposited traits for complex types and add integer enum support (#23045)
1 parent b32faa0 commit 7970d4b

36 files changed

Lines changed: 1708 additions & 97 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,10 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
614614
processParam(param, op);
615615
}
616616

617+
for (CodegenParameter param : op.queryParams) {
618+
processParam(param, op);
619+
}
620+
617621
// We keep track of the 'default' model type for this API. If there are
618622
// *any* XML responses, then we set the default to XML, otherwise we
619623
// let the default be JSON. It would be odd for an API to want to use
@@ -1064,6 +1068,25 @@ public CodegenParameter fromRequestBody(RequestBody body, Set<String> imports, S
10641068
return codegenParameter;
10651069
}
10661070

1071+
@Override
1072+
public void setParameterExampleValue(CodegenParameter codegenParameter, Parameter parameter) {
1073+
// Check whether the spec provides an example before calling super, which
1074+
// will fall back to auto-generating one from the param name/type.
1075+
boolean hasSpecExample =
1076+
parameter.getExample() != null ||
1077+
(parameter.getExamples() != null && !parameter.getExamples().isEmpty()) ||
1078+
(parameter.getSchema() != null && parameter.getSchema().getExample() != null);
1079+
1080+
super.setParameterExampleValue(codegenParameter, parameter);
1081+
1082+
if (!hasSpecExample) {
1083+
// Null out the auto-generated example so processParam can detect
1084+
// required params with no user-provided example and disable the
1085+
// client example stub accordingly.
1086+
codegenParameter.example = null;
1087+
}
1088+
}
1089+
10671090
@Override
10681091
public String getTypeDeclaration(String name) {
10691092
return "models::" + name;
@@ -1675,15 +1698,30 @@ private void processParam(CodegenParameter param, CodegenOperation op) {
16751698
example = (param.example != null) ? "&vec![\"" + param.example + "\".to_string()]" : "&Vec::new()";
16761699
} else if (param.isString) {
16771700
param.vendorExtensions.put("x-format-string", "\\\"{}\\\"");
1678-
example = "\"" + ((param.example != null) ? param.example : "") + "\".to_string()";
1701+
if (param.example != null) {
1702+
example = "\"" + param.example + "\".to_string()";
1703+
}
16791704
} else if (param.isPrimitiveType) {
16801705
if ((param.isByteArray) || (param.isBinary)) {
16811706
// Binary primitive types don't implement `Display`.
16821707
param.vendorExtensions.put("x-format-string", "{:?}");
16831708
example = "swagger::ByteArray(Vec::from(\"" + ((param.example != null) ? param.example : "") + "\"))";
1709+
} else if (param.isBoolean) {
1710+
param.vendorExtensions.put("x-format-string", "{}");
1711+
example = (param.example != null) ? param.example : "true";
16841712
} else {
16851713
param.vendorExtensions.put("x-format-string", "{}");
1686-
example = (param.example != null) ? param.example : "";
1714+
if (param.example != null) {
1715+
example = param.example;
1716+
} else if (param.isFloat || param.isDouble) {
1717+
// No example in spec: use the type zero value. This appears only in
1718+
// generated client example code.
1719+
example = "0.0";
1720+
} else if (param.isInteger || param.isLong || param.isShort || param.isNumber) {
1721+
// No example in spec: use the type zero value. This appears only in
1722+
// generated client example code.
1723+
example = "0";
1724+
}
16871725
}
16881726
} else if (param.isArray) {
16891727
param.vendorExtensions.put("x-format-string", "{:?}");

modules/openapi-generator/src/main/resources/rust-server/example-client-main.mustache

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,19 @@ fn main() {
4444
let matches = Command::new("client")
4545
.arg(Arg::new("operation")
4646
.help("Sets the operation to run")
47-
.value_parser([
47+
.value_parser(Vec::<&str>::from([
4848
{{#apiInfo}}
4949
{{#apis}}
5050
{{#operations}}
5151
{{#operation}}
52+
{{^exts.x-no-client-example}}
5253
"{{{operationId}}}",
54+
{{/exts.x-no-client-example}}
5355
{{/operation}}
5456
{{/operations}}
5557
{{/apis}}
5658
{{/apiInfo}}
57-
])
59+
]))
5860
.required(true)
5961
.index(1))
6062
.arg(Arg::new("https")

modules/openapi-generator/src/main/resources/rust-server/models.mustache

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,16 @@ impl serde_valid::validation::ValidateCompositedMinLength for {{{classname}}} {
146146
Ok(())
147147
}
148148
}
149+
150+
#[cfg(feature = "validate")]
151+
impl serde_valid::validation::ValidateCompositedMaxLength for {{{classname}}} {
152+
fn validate_composited_max_length(
153+
&self,
154+
_max_length: usize,
155+
) -> Result<(), serde_valid::validation::Composited<serde_valid::validation::error::MaxLengthError>> {
156+
Ok(())
157+
}
158+
}
149159
{{/hasConflictingModelNames}}
150160
{{#exts.x-to-string-support}}
151161
{{#exts.x-is-string}}
@@ -432,6 +442,28 @@ pub struct {{{classname}}} {
432442
{{/vars}}
433443
}
434444
445+
{{^hasConflictingModelNames}}
446+
#[cfg(feature = "validate")]
447+
impl serde_valid::validation::ValidateCompositedMinLength for {{{classname}}} {
448+
fn validate_composited_min_length(
449+
&self,
450+
_min_length: usize,
451+
) -> Result<(), serde_valid::validation::Composited<serde_valid::validation::error::MinLengthError>> {
452+
Ok(())
453+
}
454+
}
455+
456+
#[cfg(feature = "validate")]
457+
impl serde_valid::validation::ValidateCompositedMaxLength for {{{classname}}} {
458+
fn validate_composited_max_length(
459+
&self,
460+
_max_length: usize,
461+
) -> Result<(), serde_valid::validation::Composited<serde_valid::validation::error::MaxLengthError>> {
462+
Ok(())
463+
}
464+
}
465+
{{/hasConflictingModelNames}}
466+
435467
{{#vars}}
436468
{{#hasValidation}}
437469
{{#pattern}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustServerCodegenTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,27 @@ public void testIntegerParameterTypeFitting() throws IOException {
5959
// Clean up
6060
target.toFile().deleteOnExit();
6161
}
62+
63+
/**
64+
* Test that required query params without examples disable the client example.
65+
*/
66+
@Test
67+
public void testRequiredQueryParamWithoutExampleDisablesClientExample() throws IOException {
68+
Path target = Files.createTempDirectory("test");
69+
final CodegenConfigurator configurator = new CodegenConfigurator()
70+
.setGeneratorName("rust-server")
71+
.setInputSpec("src/test/resources/3_0/rust-server/openapi-v3.yaml")
72+
.setSkipOverwrite(false)
73+
.setOutputDir(target.toAbsolutePath().toString().replace("\\", "/"));
74+
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
75+
files.forEach(File::deleteOnExit);
76+
77+
Path exampleClientMain = Path.of(target.toString(), "/examples/client/main.rs");
78+
TestUtils.assertFileExists(exampleClientMain);
79+
TestUtils.assertFileContains(exampleClientMain, "Disabled because there's no example.");
80+
TestUtils.assertFileContains(exampleClientMain, "Some(\"QueryExampleGet\")");
81+
82+
// Clean up
83+
target.toFile().deleteOnExit();
84+
}
6285
}

modules/openapi-generator/src/test/resources/2_0/rust-server/rust-server-test.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ paths:
108108
description: OK
109109
schema:
110110
$ref: '#/definitions/allOfObject'
111-
112111
parameters:
113112
nested_response:
114113
name: nested_response

modules/openapi-generator/src/test/resources/3_0/rust-server/openapi-v3.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ paths:
8080
application/json:
8181
schema:
8282
$ref: "#/components/schemas/anotherXmlObject"
83+
/query-example:
84+
get:
85+
summary: Test required query params with and without examples
86+
operationId: queryExampleGet
87+
parameters:
88+
- name: required_no_example
89+
in: query
90+
required: true
91+
schema:
92+
type: string
93+
- name: required_with_example
94+
in: query
95+
required: true
96+
example: 42
97+
schema:
98+
type: integer
99+
format: int32
100+
responses:
101+
200:
102+
description: OK
83103
/multiget:
84104
get:
85105
summary: Get some stuff.

samples/server/petstore/rust-server-deprecated/output/openapi-v3/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ cargo run --example openapi-v3-client MultipleAuthSchemeGet
100100
cargo run --example openapi-v3-client OneOfGet
101101
cargo run --example openapi-v3-client OverrideServerGet
102102
cargo run --example openapi-v3-client ParamgetGet
103+
cargo run --example openapi-v3-client QueryExampleGet
103104
cargo run --example openapi-v3-client ReadonlyAuthSchemeGet
104105
cargo run --example openapi-v3-client RegisterCallbackPost
105106
cargo run --example openapi-v3-client RequiredOctetStreamPut
@@ -166,6 +167,7 @@ Method | HTTP request | Description
166167
[****](docs/default_api.md#) | **GET** /one-of |
167168
[****](docs/default_api.md#) | **GET** /override-server |
168169
[****](docs/default_api.md#) | **GET** /paramget | Get some stuff with parameters.
170+
[**queryExampleGet**](docs/default_api.md#queryExampleGet) | **GET** /query-example | Test required query params with and without examples
169171
[****](docs/default_api.md#) | **GET** /readonly_auth_scheme |
170172
[****](docs/default_api.md#) | **POST** /register-callback |
171173
[****](docs/default_api.md#) | **PUT** /required_octet_stream |

samples/server/petstore/rust-server-deprecated/output/openapi-v3/api/openapi.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,30 @@ paths:
6767
$ref: "#/components/schemas/anotherXmlObject"
6868
description: JSON rsp
6969
summary: Get some stuff with parameters.
70+
/query-example:
71+
get:
72+
operationId: queryExampleGet
73+
parameters:
74+
- explode: true
75+
in: query
76+
name: required_no_example
77+
required: true
78+
schema:
79+
type: string
80+
style: form
81+
- example: 42
82+
explode: true
83+
in: query
84+
name: required_with_example
85+
required: true
86+
schema:
87+
format: int32
88+
type: integer
89+
style: form
90+
responses:
91+
"200":
92+
description: OK
93+
summary: Test required query params with and without examples
7094
/multiget:
7195
get:
7296
responses:

samples/server/petstore/rust-server-deprecated/output/openapi-v3/bin/cli.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use openapi_v3::{
1919
OneOfGetResponse,
2020
OverrideServerGetResponse,
2121
ParamgetGetResponse,
22+
QueryExampleGetResponse,
2223
ReadonlyAuthSchemeGetResponse,
2324
RegisterCallbackPostResponse,
2425
RequiredOctetStreamPutResponse,
@@ -151,6 +152,11 @@ enum Operation {
151152
#[structopt(parse(try_from_str = parse_json), long)]
152153
some_list: Option<Vec<models::MyId>>,
153154
},
155+
/// Test required query params with and without examples
156+
QueryExampleGet {
157+
required_no_example: String,
158+
required_with_example: i32,
159+
},
154160
ReadonlyAuthSchemeGet {
155161
},
156162
RegisterCallbackPost {
@@ -546,6 +552,24 @@ async fn main() -> Result<()> {
546552
&serde_json::to_string_pretty(&body)?,
547553
}
548554
}
555+
Operation::QueryExampleGet {
556+
required_no_example,
557+
required_with_example,
558+
} => {
559+
info!("Performing a QueryExampleGet request");
560+
561+
let result = client.query_example_get(
562+
required_no_example,
563+
required_with_example,
564+
).await?;
565+
debug!("Result: {:?}", result);
566+
567+
match result {
568+
QueryExampleGetResponse::OK
569+
=> "OK\n".to_string()
570+
,
571+
}
572+
}
549573
Operation::ReadonlyAuthSchemeGet {
550574
} => {
551575
info!("Performing a ReadonlyAuthSchemeGet request");

samples/server/petstore/rust-server-deprecated/output/openapi-v3/docs/default_api.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Method | HTTP request | Description
1818
****](default_api.md#) | **GET** /one-of |
1919
****](default_api.md#) | **GET** /override-server |
2020
****](default_api.md#) | **GET** /paramget | Get some stuff with parameters.
21+
**queryExampleGet**](default_api.md#queryExampleGet) | **GET** /query-example | Test required query params with and without examples
2122
****](default_api.md#) | **GET** /readonly_auth_scheme |
2223
****](default_api.md#) | **POST** /register-callback |
2324
****](default_api.md#) | **PUT** /required_octet_stream |
@@ -412,6 +413,32 @@ No authorization required
412413

413414
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
414415

416+
# **queryExampleGet**
417+
> queryExampleGet(required_no_example, required_with_example)
418+
Test required query params with and without examples
419+
420+
### Required Parameters
421+
422+
Name | Type | Description | Notes
423+
------------- | ------------- | ------------- | -------------
424+
**required_no_example** | **String**| |
425+
**required_with_example** | **i32**| |
426+
427+
### Return type
428+
429+
(empty response body)
430+
431+
### Authorization
432+
433+
No authorization required
434+
435+
### HTTP request headers
436+
437+
- **Content-Type**: Not defined
438+
- **Accept**: Not defined
439+
440+
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
441+
415442
# ****
416443
> (ctx, )
417444

0 commit comments

Comments
 (0)