Skip to content

Commit

Permalink
Update swagger generated API crates (#590)
Browse files Browse the repository at this point in the history
## Type of change
```
- [ ] Bug fix
- [ ] New feature development
- [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other
```

## Objective
Update swagger generator to version 7.2.0, pull in the new templates and
generate the new API crates.

I've also updated the build instructions to use .NET 8, as is required
in the latest server code.

The version of the server code used to generate this is
bitwarden/server@fc1d7c7
I've had to make some small modifications to the server code to get it
working, ~~and I'll link the PR here shortly.~~
bitwarden/server#3760

To make this easier to review, I've split this into three commits:
- b42f189: Update the swagger and
dotnet versions, and pull in the new templates as-is. Note that this
won't generate working code.
- 9727d46: Patched the provided
templates to make it work with our code. This is in a separate commit to
make it easier to see what we've changed and hopefully make it easier in
the future to update these templates. The changes are:
    - Ignore warnings by default 
    - Use Uuids instead of strings
    - Use serde_repr to support int enums
    - Disabled default features for reqwest (to remove default tls)
- Removing double optionals (This could be reverted once we're better at
marking everything as required in the server)
- ~~f9b63606477d0f0f987b8534ef6e6755c76003a2: New autogenerated swagger
code, no manually edited files here.~~ Moved to a separate PR (#593)
  • Loading branch information
dani-garcia authored Feb 8, 2024
1 parent 575dd19 commit f16189c
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 88 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ The first step is to generate the swagger documents from the server repository.

```bash
# src/Api
dotnet swagger tofile --output ../../api.json ./bin/Debug/net6.0/Api.dll internal
dotnet swagger tofile --output ../../api.json ./bin/Debug/net8.0/Api.dll internal

# src/Identity
ASPNETCORE_ENVIRONMENT=development dotnet swagger tofile --output ../../identity.json ./bin/Debug/net6.0/Identity.dll v1
ASPNETCORE_ENVIRONMENT=development dotnet swagger tofile --output ../../identity.json ./bin/Debug/net8.0/Identity.dll v1
```

### OpenApi Generator
Expand Down
2 changes: 1 addition & 1 deletion openapitools.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "6.5.0"
"version": "7.2.0"
}
}
55 changes: 42 additions & 13 deletions support/openapi-template/Cargo.mustache
Original file line number Diff line number Diff line change
@@ -1,42 +1,71 @@
[package]
name = "{{{packageName}}}"
version = "{{#lambdaVersion}}{{{packageVersion}}}{{/lambdaVersion}}"
{{#infoEmail}}
authors = ["{{{.}}}"]
{{/infoEmail}}
{{^infoEmail}}
authors = ["OpenAPI Generator team and contributors"]
{{/infoEmail}}
{{#appDescription}}
description = "{{{.}}}"
{{/appDescription}}
{{#licenseInfo}}
license = "{{.}}"
{{/licenseInfo}}
{{^licenseInfo}}
# Override this license by providing a License Object in the OpenAPI.
license = "Unlicense"
{{/licenseInfo}}
edition = "2018"
{{#publishRustRegistry}}
publish = ["{{.}}"]
{{/publishRustRegistry}}
{{#repositoryUrl}}
repository = "{{.}}"
{{/repositoryUrl}}
{{#documentationUrl}}
documentation = "{{.}}"
{{/documentationUrl}}
{{#homePageUrl}}
homepage = "{{.}}
{{/homePageUrl}}

[dependencies]
serde = "^1.0"
serde_derive = "^1.0"
{{#serdeWith}}
serde_with = "^2.0"
{{/serdeWith}}
serde_json = "^1.0"
serde_repr = "^0.1"
url = "^2.2"
uuid = { version = "^1.0", features = ["serde"] }
uuid = { version = "^1.0", features = ["serde", "v4"] }
{{#hyper}}
hyper = { version = "~0.14", features = ["full"] }
hyper-tls = "~0.5"
http = "~0.2"
serde_yaml = "0.7"
base64 = "~0.7.0"
futures = "^0.3"
{{/hyper}}
{{#withAWSV4Signature}}
aws-sigv4 = "0.3.0"
http = "0.2.5"
secrecy = "0.8.0"
{{/withAWSV4Signature}}
{{#reqwest}}
{{^supportAsync}}
reqwest = "~0.9"
[dependencies.reqwest]
version = "^0.11"
features = ["json", "blocking", "multipart"]
{{/supportAsync}}
{{#supportAsync}}
{{#supportMiddleware}}
reqwest-middleware = "0.2.0"
{{/supportMiddleware}}
[dependencies.reqwest]
version = "^0.11"
features = ["json", "multipart"]
default-features = false
{{/supportAsync}}
{{/reqwest}}
{{#withAWSV4Signature}}
aws-sigv4 = "0.3.0"
http = "0.2.5"
secrecy = "0.8.0"
{{/withAWSV4Signature}}

[dev-dependencies]
{{#hyper}}
tokio-core = "*"
{{/hyper}}
Empty file modified support/openapi-template/git_push.sh.mustache
100644 → 100755
Empty file.
16 changes: 8 additions & 8 deletions support/openapi-template/hyper/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl<C: hyper::client::connect::Connect> {{{classname}}}Client<C>
pub trait {{{classname}}} {
{{#operations}}
{{#operation}}
fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}&str{{/isString}}{{#isUuid}}uuid::Uuid{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}crate::models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin<Box<dyn Future<Output = Result<{{^returnType}}(){{/returnType}}{{#returnType}}{{{returnType}}}{{/returnType}}, Error>>>>;
fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}{{^isUuid}}&str{{/isUuid}}{{/isString}}{{#isUuid}}&str{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}crate::models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin<Box<dyn Future<Output = Result<{{^returnType}}(){{/returnType}}{{#returnType}}{{{returnType}}}{{/returnType}}, Error>>>>;
{{/operation}}
{{/operations}}
}
Expand All @@ -38,7 +38,7 @@ impl<C: hyper::client::connect::Connect>{{{classname}}} for {{{classname}}}Clien
{{#operations}}
{{#operation}}
#[allow(unused_mut)]
fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}&str{{/isString}}{{#isUuid}}uuid::Uuid{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}crate::models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin<Box<dyn Future<Output = Result<{{^returnType}}(){{/returnType}}{{#returnType}}{{{.}}}{{/returnType}}, Error>>>> {
fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}{{^isUuid}}&str{{/isUuid}}{{/isString}}{{#isUuid}}&str{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}crate::models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin<Box<dyn Future<Output = Result<{{^returnType}}(){{/returnType}}{{#returnType}}{{{.}}}{{/returnType}}, Error>>>> {
let mut req = __internal_request::Request::new(hyper::Method::{{{httpMethod.toUpperCase}}}, "{{{path}}}".to_string())
{{#hasAuthMethods}}
{{#authMethods}}
Expand All @@ -49,9 +49,9 @@ impl<C: hyper::client::connect::Connect>{{{classname}}} for {{{classname}}}Clien
param_name: "{{{keyParamName}}}".to_owned(),
}))
{{/isApiKey}}
{{#isBasic}}
{{#isBasicBasic}}
.with_auth(__internal_request::Auth::Basic)
{{/isBasic}}
{{/isBasicBasic}}
{{#isOAuth}}
.with_auth(__internal_request::Auth::Oauth)
{{/isOAuth}}
Expand All @@ -66,7 +66,7 @@ impl<C: hyper::client::connect::Connect>{{{classname}}} for {{{classname}}}Clien
{{#isNullable}}
match {{{paramName}}} {
Some(param_value) => { req = req.with_query_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); },
None => { req = req.with_query_param("{{{baseName}}}".to_string(), String::new()); },
None => { req = req.with_query_param("{{{baseName}}}".to_string(), "".to_string()); },
}
{{/isNullable}}
{{/required}}
Expand All @@ -85,7 +85,7 @@ impl<C: hyper::client::connect::Connect>{{{classname}}} for {{{classname}}}Clien
{{#isNullable}}
match {{{paramName}}} {
Some(param_value) => { req = req.with_path_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); },
None => { req = req.with_path_param("{{{baseName}}}".to_string(), String::new()); },
None => { req = req.with_path_param("{{{baseName}}}".to_string(), "".to_string()); },
}
{{/isNullable}}
{{/required}}
Expand All @@ -104,7 +104,7 @@ impl<C: hyper::client::connect::Connect>{{{classname}}} for {{{classname}}}Clien
{{#isNullable}}
match {{{paramName}}} {
Some(param_value) => { req = req.with_header_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); },
None => { req = req.with_header_param("{{{baseName}}}".to_string(), String::new()); },
None => { req = req.with_header_param("{{{baseName}}}".to_string(), "".to_string()); },
}
{{/isNullable}}
{{/required}}
Expand Down Expand Up @@ -143,7 +143,7 @@ impl<C: hyper::client::connect::Connect>{{{classname}}} for {{{classname}}}Clien
{{#isNullable}}
match {{{paramName}}} {
Some(param_value) => { req = req.with_form_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); },
None => { req = req.with_form_param("{{{baseName}}}".to_string(), String::new()); },
None => { req = req.with_form_param("{{{baseName}}}".to_string(), "".to_string()); },
}
{{/isNullable}}
{{/required}}
Expand Down
2 changes: 1 addition & 1 deletion support/openapi-template/model.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub enum {{{classname}}} {
{{!-- for non-enum schemas --}}
{{^isEnum}}
{{^discriminator}}
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct {{{classname}}} {
{{#vars}}
{{#description}}
Expand Down
93 changes: 43 additions & 50 deletions support/openapi-template/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use std::collections::HashMap;
use std::pin::Pin;

use futures;
use futures::future::*;
use futures::Future;
use futures::future::*;
use hyper;
use hyper::header::{HeaderValue, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT};
use hyper::header::{AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, HeaderValue, USER_AGENT};
use serde;
use serde_json;

Expand Down Expand Up @@ -50,6 +50,7 @@ pub(crate) struct Request {
serialized_body: Option<String>,
}

#[allow(dead_code)]
impl Request {
pub fn new(method: hyper::Method, path: String) -> Self {
Request {
Expand All @@ -75,16 +76,19 @@ impl Request {
self
}

#[allow(unused)]
pub fn with_query_param(mut self, basename: String, param: String) -> Self {
self.query_params.insert(basename, param);
self
}

#[allow(unused)]
pub fn with_path_param(mut self, basename: String, param: String) -> Self {
self.path_params.insert(basename, param);
self
}

#[allow(unused)]
pub fn with_form_param(mut self, basename: String, param: String) -> Self {
self.form_params.insert(basename, param);
self
Expand All @@ -103,13 +107,13 @@ impl Request {
pub fn execute<'a, C, U>(
self,
conf: &configuration::Configuration<C>,
) -> Pin<Box<dyn Future<Output = Result<U, Error>> + 'a>>
where
C: hyper::client::connect::Connect + Clone + std::marker::Send + Sync,
U: Sized + std::marker::Send + 'a,
for<'de> U: serde::Deserialize<'de>,
) -> Pin<Box<dyn Future<Output=Result<U, Error>> + 'a>>
where
C: hyper::client::connect::Connect + Clone + std::marker::Send + Sync,
U: Sized + std::marker::Send + 'a,
for<'de> U: serde::Deserialize<'de>,
{
let mut query_string = ::url::form_urlencoded::Serializer::new(String::new());
let mut query_string = ::url::form_urlencoded::Serializer::new("".to_owned());

let mut path = self.path;
for (k, v) in self.path_params {
Expand All @@ -133,7 +137,9 @@ impl Request {
Ok(u) => u,
};

let mut req_builder = hyper::Request::builder().uri(uri).method(self.method);
let mut req_builder = hyper::Request::builder()
.uri(uri)
.method(self.method);

// Detect the authorization type if it hasn't been set.
let auth = self.auth.unwrap_or_else(||
Expand Down Expand Up @@ -180,13 +186,10 @@ impl Request {
}

if let Some(ref user_agent) = conf.user_agent {
req_builder = req_builder.header(
USER_AGENT,
match HeaderValue::from_str(user_agent) {
Ok(header_value) => header_value,
Err(e) => return Box::pin(futures::future::err(super::Error::Header(e))),
},
);
req_builder = req_builder.header(USER_AGENT, match HeaderValue::from_str(user_agent) {
Ok(header_value) => header_value,
Err(e) => return Box::pin(futures::future::err(super::Error::Header(e)))
});
}

for (k, v) in self.header_params {
Expand All @@ -195,11 +198,8 @@ impl Request {

let req_headers = req_builder.headers_mut().unwrap();
let request_result = if self.form_params.len() > 0 {
req_headers.insert(
CONTENT_TYPE,
HeaderValue::from_static("application/ x-www-form-urlencoded"),
);
let mut enc = ::url::form_urlencoded::Serializer::new(String::new());
req_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/x-www-form-urlencoded"));
let mut enc = ::url::form_urlencoded::Serializer::new("".to_owned());
for (k, v) in self.form_params {
enc.append_pair(&k, &v);
}
Expand All @@ -213,37 +213,30 @@ impl Request {
};
let request = match request_result {
Ok(request) => request,
Err(e) => return Box::pin(futures::future::err(Error::from(e))),
Err(e) => return Box::pin(futures::future::err(Error::from(e)))
};

let no_return_type = self.no_return_type;
Box::pin(
conf.client
.request(request)
.map_err(Error::from)
.and_then(move |response| {
let status = response.status();
if !status.is_success() {
futures::future::err::<U, Error>(Error::from((
status,
response.into_body(),
)))
.boxed()
} else if no_return_type {
// This is a hack; if there's no_ret_type, U is (), but serde_json gives an
// error when deserializing "" into (), so deserialize 'null' into it
// instead.
// An alternate option would be to require U: Default, and then return
// U::default() here instead since () implements that, but then we'd
// need to impl default for all models.
futures::future::ok::<U, Error>(serde_json::Value::Null).boxed()
} else {
hyper::body::to_bytes(response.into_body())
.map(|bytes| serde_json::from_slice(&bytes.unwrap()))
.map_err(Error::from)
.boxed()
}
}),
)
Box::pin(conf.client
.request(request)
.map_err(|e| Error::from(e))
.and_then(move |response| {
let status = response.status();
if !status.is_success() {
futures::future::err::<U, Error>(Error::from((status, response.into_body()))).boxed()
} else if no_return_type {
// This is a hack; if there's no_ret_type, U is (), but serde_json gives an
// error when deserializing "" into (), so deserialize 'null' into it
// instead.
// An alternate option would be to require U: Default, and then return
// U::default() here instead since () implements that, but then we'd
// need to impl default for all models.
futures::future::ok::<U, Error>(serde_json::from_str("null").expect("serde null value")).boxed()
} else {
hyper::body::to_bytes(response.into_body())
.map(|bytes| serde_json::from_slice(&bytes.unwrap()))
.map_err(|e| Error::from(e)).boxed()
}
}))
}
}
Loading

0 comments on commit f16189c

Please sign in to comment.