Skip to content

Commit

Permalink
Added client generated out of OpenAPI schema.
Browse files Browse the repository at this point in the history
  • Loading branch information
piohei committed Sep 12, 2024
1 parent e67fa99 commit 72a501d
Show file tree
Hide file tree
Showing 90 changed files with 4,062 additions and 813 deletions.
510 changes: 454 additions & 56 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ aws-types = "1.2.1"

# Internal
postgres-docker-utils = { path = "crates/postgres-docker-utils" }
base-api-types = { path = "crates/base-api-types" }
tx-sitter-client = { path = "crates/tx-sitter-client" }

# Company
telemetry-batteries = { git = "https://github.com/worldcoin/telemetry-batteries", rev = "e0891328b29d9f85df037633feccca2f74a291a6" }
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,26 @@ Therefore I recommend [cargo-nextest](https://nexte.st/) as it runs all the test
cargo nextest run --workspace
```
can be used instead.
## Client
Client crate is located in `creates/tx-sitter-client`. It is generated using official OpenAPI generator with modified template files. Modified template files are located in `client-template/` directory. Possible files to overwrite could be fined here https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/rust.
To generate client OpenAPI spec schema is required. To get one just run tx-sitter and then call `/schema.yaml` endpoint. To download schema you can use curl for example:
```shell
curl http://localhost:3000/schema.yml > schema.yaml
```

Client generation is done by using default OpenAPI tools. You can install generator or use docker image as shown below:

```shell
docker run --rm -v "${PWD}:/local" --user "$(id -u):$(id -g)" -- openapitools/openapi-generator-cli generate \
-i /local/schema.yaml \
-g rust \
-o /local/crates/tx-sitter-client \
-t /local/client-template \
--skip-validate-spec \
--additional-properties=packageName=tx-sitter-client,supportMiddleware=true,useSingleRequestParameter=true,avoidBoxedModels=true \
--type-mappings=address=base_api_types::Address,decimal-u256=base_api_types::DecimalU256,h256=base_api_types::H256,bytes=base_api_types::HexBytes,hex-u256=base_api_types::HexU256
```
76 changes: 76 additions & 0 deletions client-template/Cargo.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
[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 = "2021"
{{#publishRustRegistry}}
publish = ["{{.}}"]
{{/publishRustRegistry}}
{{#repositoryUrl}}
repository = "{{.}}"
{{/repositoryUrl}}
{{#documentationUrl}}
documentation = "{{.}}"
{{/documentationUrl}}
{{#homePageUrl}}
homepage = "{{.}}
{{/homePageUrl}}

[dependencies]
serde = { version = "^1.0", features = ["derive"] }
base-api-types = { path = "../base-api-types" }
{{#serdeWith}}
serde_with = { version = "^3.8", default-features = false, features = ["base64", "std", "macros"] }
{{/serdeWith}}
serde_json = "^1.0"
serde_repr = "^0.1"
url = "^2.5"
uuid = { version = "^1.8", features = ["serde", "v4"] }
{{#hyper}}
{{#hyper0x}}
hyper = { version = "~0.14", features = ["full"] }
hyper-tls = "~0.5"
{{/hyper0x}}
{{^hyper0x}}
hyper = { version = "^1.3.1", features = ["full"] }
hyper-util = { version = "0.1.5", features = ["client", "client-legacy", "http1", "http2"] }
http-body-util = { version = "0.1.2" }
{{/hyper0x}}
http = "~0.2"
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 = { version = "^0.12", features = ["json", "blocking", "multipart"] }
{{#supportMiddleware}}
reqwest-middleware = { version = "^0.3", features = ["json", "blocking", "multipart"] }
{{/supportMiddleware}}
{{/supportAsync}}
{{#supportAsync}}
reqwest = { version = "^0.12", features = ["json", "multipart"] }
{{#supportMiddleware}}
reqwest-middleware = { version = "^0.3", features = ["json", "multipart"] }
{{/supportMiddleware}}
{{/supportAsync}}
{{/reqwest}}
57 changes: 57 additions & 0 deletions client-template/README.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Rust API client for {{{packageName}}}

> [!CAUTION]
> This whole client is generated. Please read README in top directory of repo.

{{#appDescriptionWithNewLines}}
{{{.}}}
{{/appDescriptionWithNewLines}}

{{#infoUrl}}
For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})
{{/infoUrl}}

## Overview

This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://openapis.org) from a remote server, you can easily generate an API client.

- API version: {{{appVersion}}}
- Package version: {{{packageVersion}}}
{{^hideGenerationTimestamp}}
- Build date: {{{generatedDate}}}
{{/hideGenerationTimestamp}}
- Generator version: {{generatorVersion}}
- Build package: `{{{generatorClass}}}`

## Installation

Put the package under your project folder in a directory named `{{packageName}}` and add the following to `Cargo.toml` under `[dependencies]`:

```
{{{packageName}}} = { path = "./{{{packageName}}}" }
```

## Documentation for API Endpoints

All URIs are relative to *{{{basePath}}}*

Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{{classname}}}* | [**{{{operationId}}}**]({{{apiDocPath}}}{{classname}}.md#{{{operationIdLowerCase}}}) | **{{{httpMethod}}}** {{{path}}} | {{{summary}}}
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}

## Documentation For Models

{{#models}}{{#model}} - [{{{classname}}}]({{{modelDocPath}}}{{{classname}}}.md)
{{/model}}{{/models}}

To get access to the crate's generated documentation, use:

```
cargo doc --open
```

## Author

{{#apiInfo}}{{#apis}}{{#-last}}{{{infoEmail}}}
{{/-last}}{{/apis}}{{/apiInfo}}
141 changes: 141 additions & 0 deletions client-template/reqwest/configuration.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
{{>partial_header}}

{{#withAWSV4Signature}}
use std::time::SystemTime;
use aws_sigv4::http_request::{sign, SigningSettings, SigningParams, SignableRequest};
use http;
use secrecy::{SecretString, ExposeSecret};
{{/withAWSV4Signature}}

#[derive(Debug, Clone)]
pub struct Configuration {
pub base_path: String,
pub user_agent: Option<String>,
pub client: {{#supportMiddleware}}reqwest_middleware::ClientWithMiddleware{{/supportMiddleware}}{{^supportMiddleware}}reqwest{{^supportAsync}}::blocking{{/supportAsync}}::Client{{/supportMiddleware}},
pub basic_auth: Option<BasicAuth>,
pub oauth_access_token: Option<String>,
pub bearer_access_token: Option<String>,
pub api_key: Option<ApiKey>,
{{#withAWSV4Signature}}
pub aws_v4_key: Option<AWSv4Key>,
{{/withAWSV4Signature}}
// TODO: take an oauth2 token source, similar to the go one
}

pub type BasicAuth = (String, Option<String>);

#[derive(Debug, Clone)]
pub struct ApiKey {
pub prefix: Option<String>,
pub key: String,
}

{{#withAWSV4Signature}}
#[derive(Debug, Clone)]
pub struct AWSv4Key {
pub access_key: String,
pub secret_key: SecretString,
pub region: String,
pub service: String,
}

impl AWSv4Key {
pub fn sign(&self, uri: &str, method: &str, body: &str) -> Result<Vec::<(String, String)>, aws_sigv4::http_request::Error> {
let request = http::Request::builder()
.uri(uri)
.method(method)
.body(body).unwrap();
let signing_settings = SigningSettings::default();
let signing_params = SigningParams::builder()
.access_key(self.access_key.as_str())
.secret_key(self.secret_key.expose_secret().as_str())
.region(self.region.as_str())
.service_name(self.service.as_str())
.time(SystemTime::now())
.settings(signing_settings)
.build()
.unwrap();
let signable_request = SignableRequest::from(&request);
let (mut signing_instructions, _signature) = sign(signable_request, &signing_params)?.into_parts();
let mut additional_headers = Vec::<(String, String)>::new();
if let Some(new_headers) = signing_instructions.take_headers() {
for (name, value) in new_headers.into_iter() {
additional_headers.push((name.expect("header should have name").to_string(),
value.to_str().expect("header value should be a string").to_string()));
}
}
Ok(additional_headers)
}
}
{{/withAWSV4Signature}}

impl Default for Configuration {
fn default() -> Self {
Configuration {
base_path: "{{{basePath}}}".to_owned(),
user_agent: {{#httpUserAgent}}Some("{{{.}}}".to_owned()){{/httpUserAgent}}{{^httpUserAgent}}Some("OpenAPI-Generator/{{{version}}}/rust".to_owned()){{/httpUserAgent}},
client: {{#supportMiddleware}}reqwest_middleware::ClientBuilder::new(reqwest{{^supportAsync}}::blocking{{/supportAsync}}::Client::new()).build(){{/supportMiddleware}}{{^supportMiddleware}}reqwest{{^supportAsync}}::blocking{{/supportAsync}}::Client::new(){{/supportMiddleware}},
basic_auth: None,
oauth_access_token: None,
bearer_access_token: None,
api_key: None,
{{#withAWSV4Signature}} aws_v4_key: None,{{/withAWSV4Signature}}
}
}
}

pub struct ConfigurationBuilder {
pub base_path: Option<String>,
pub user_agent: Option<String>,
pub basic_auth: Option<BasicAuth>,
}

impl ConfigurationBuilder {
pub fn new() -> ConfigurationBuilder {
ConfigurationBuilder {
base_path: None,
user_agent: None,
basic_auth: None,
}
}

pub fn base_path(mut self, base_path: String) -> Self {
self.base_path = Some(base_path);
self
}

pub fn user_agent(mut self, user_agent: String) -> Self {
self.user_agent = Some(user_agent);
self
}

pub fn basic_auth(mut self, user: String, pass: Option<String>) -> Self {
self.basic_auth = Some((user, pass));
self
}

pub fn build(self) -> Configuration {
let mut conf: Configuration = Default::default();
if let Some(base_path) = self.base_path {
conf.base_path = base_path;
}

if let Some(user_agent) = self.user_agent {
conf.user_agent = Some(user_agent);
}

if let Some(basic_auth) = self.basic_auth {
conf.basic_auth = Some(basic_auth);
}

conf
}
}

impl Default for ConfigurationBuilder {
fn default() -> Self {
Self::new()
}
}

23 changes: 23 additions & 0 deletions crates/base-api-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "base-api-types"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
ethers = { version = "2.0.11" }
poem = { version = "3", features = ["eyre06"] }
poem-openapi = { version = "5", features = [
"openapi-explorer",
"rapidoc",
"redoc",
"swagger-ui",
] }
serde = "1.0.136"
serde_json = "1.0.91"

[dev-dependencies]
hex = "0.4.3"
hex-literal = "0.4.1"
test-case = "3.1.0"
Loading

0 comments on commit 72a501d

Please sign in to comment.