├── .gitignore ├── src ├── models │ ├── mod.rs │ └── todo.rs ├── services │ ├── mod.rs │ ├── openapi.rs │ └── todo.rs ├── main.rs └── error.rs ├── cover.png ├── swagger.png ├── background.png ├── docker_image_ls.png ├── rustfmt.toml ├── .vscode └── settings.json ├── Cargo.toml ├── Dockerfile ├── readme.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /src/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod todo; 2 | -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xle0ne/ntex-rest-api-example/HEAD/cover.png -------------------------------------------------------------------------------- /swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xle0ne/ntex-rest-api-example/HEAD/swagger.png -------------------------------------------------------------------------------- /background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xle0ne/ntex-rest-api-example/HEAD/background.png -------------------------------------------------------------------------------- /docker_image_ls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xle0ne/ntex-rest-api-example/HEAD/docker_image_ls.png -------------------------------------------------------------------------------- /src/services/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod todo; 2 | pub mod openapi; 3 | 4 | use ntex::web; 5 | 6 | pub async fn default() -> web::HttpResponse { 7 | web::HttpResponse::NotFound().finish() 8 | } 9 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | indent_style = "Block" 2 | max_width = 80 3 | tab_spaces = 2 4 | reorder_imports = false 5 | reorder_modules = false 6 | force_multiline_blocks = true 7 | brace_style = "PreferSameLine" 8 | control_brace_style = "AlwaysSameLine" 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.rulers": [ 3 | 80 4 | ], 5 | "editor.tabSize": 2, 6 | "editor.detectIndentation": false, 7 | "editor.trimAutoWhitespace": true, 8 | "editor.formatOnSave": true, 9 | "files.insertFinalNewline": true, 10 | "files.trimTrailingWhitespace": true, 11 | "rust-analyzer.showUnlinkedFileNotification": false, 12 | "rust-analyzer.checkOnSave": true, 13 | "rust-analyzer.check.command": "clippy" 14 | } 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "my-rest-api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | [profile.release] 8 | opt-level = "z" 9 | codegen-units = 1 10 | strip = true 11 | lto = true 12 | 13 | [dependencies] 14 | ntex = { version = "0.6.7", features = ["tokio"] } 15 | serde = { version = "1.0.163", features = ["derive"] } 16 | serde_json = "1.0.96" 17 | utoipa = "3.3.0" 18 | utoipa-swagger-ui = "3.1.3" 19 | -------------------------------------------------------------------------------- /src/models/todo.rs: -------------------------------------------------------------------------------- 1 | use utoipa::ToSchema; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// Todo model 5 | #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] 6 | pub struct Todo { 7 | /// The todo id 8 | pub id: i32, 9 | /// The todo title 10 | pub title: String, 11 | /// The todo completed status 12 | pub completed: bool, 13 | } 14 | 15 | /// Partial Todo model 16 | #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] 17 | pub struct TodoPartial { 18 | /// The todo title 19 | pub title: String, 20 | } 21 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use ntex::web; 2 | 3 | mod error; 4 | mod models; 5 | mod services; 6 | 7 | #[ntex::main] 8 | async fn main() -> std::io::Result<()> { 9 | web::server(|| { 10 | web::App::new() 11 | // Register swagger endpoints 12 | .configure(services::openapi::ntex_config) 13 | // Register todo endpoints 14 | .configure(services::todo::ntex_config) 15 | // Default endpoint for unregisterd endpoints 16 | .default_service(web::route().to(services::default)) 17 | }) 18 | .bind(("0.0.0.0", 8080))? 19 | .run() 20 | .await?; 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Builder 2 | FROM rust:1.69.0-alpine3.17 as builder 3 | 4 | WORKDIR /app 5 | 6 | ## Install build dependencies 7 | RUN apk add alpine-sdk musl-dev build-base upx 8 | 9 | ## Copy source code 10 | COPY Cargo.toml Cargo.lock ./ 11 | COPY src ./src 12 | 13 | ## Build release binary 14 | RUN cargo build --release --target x86_64-unknown-linux-musl 15 | ## Pack release binary with UPX (optional) 16 | RUN upx --best --lzma /app/target/x86_64-unknown-linux-musl/release/my-rest-api 17 | 18 | # Runtime 19 | FROM scratch 20 | 21 | ## Copy release binary from builder 22 | COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/my-rest-api /app 23 | 24 | ENTRYPOINT ["/app"] 25 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use ntex::web; 2 | use ntex::http; 3 | use utoipa::ToSchema; 4 | use serde::{Serialize, Deserialize}; 5 | 6 | /// An http error response 7 | #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] 8 | pub struct HttpError { 9 | /// The error message 10 | pub msg: String, 11 | /// The http status code, skipped in serialization 12 | #[serde(skip)] 13 | pub status: http::StatusCode, 14 | } 15 | 16 | /// Helper function to display an HttpError 17 | impl std::fmt::Display for HttpError { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | write!(f, "[{}] {}", self.status, self.msg) 20 | } 21 | } 22 | 23 | /// Implement standard error for HttpError 24 | impl std::error::Error for HttpError {} 25 | 26 | /// Helper function to convert an HttpError into a ntex::web::HttpResponse 27 | impl web::WebResponseError for HttpError { 28 | fn error_response(&self, _: &web::HttpRequest) -> web::HttpResponse { 29 | web::HttpResponse::build(self.status).json(&self) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/services/openapi.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ntex::web; 4 | use ntex::http; 5 | use ntex::util::Bytes; 6 | use utoipa::OpenApi; 7 | 8 | use crate::error::HttpError; 9 | use crate::models::todo::{Todo, TodoPartial}; 10 | 11 | use super::todo; 12 | 13 | /// Main structure to generate OpenAPI documentation 14 | #[derive(OpenApi)] 15 | #[openapi( 16 | paths( 17 | todo::get_todos, 18 | todo::create_todo, 19 | todo::get_todo, 20 | todo::update_todo, 21 | todo::delete_todo, 22 | ), 23 | components(schemas(Todo, TodoPartial, HttpError)) 24 | )] 25 | pub(crate) struct ApiDoc; 26 | 27 | #[web::get("/{tail}*")] 28 | async fn get_swagger( 29 | tail: web::types::Path, 30 | openapi_conf: web::types::State>>, 31 | ) -> Result { 32 | if tail.as_ref() == "swagger.json" { 33 | let spec = ApiDoc::openapi().to_json().map_err(|err| HttpError { 34 | status: http::StatusCode::INTERNAL_SERVER_ERROR, 35 | msg: format!("Error generating OpenAPI spec: {}", err), 36 | })?; 37 | return Ok( 38 | web::HttpResponse::Ok() 39 | .content_type("application/json") 40 | .body(spec), 41 | ); 42 | } 43 | let conf = openapi_conf.as_ref().clone(); 44 | match utoipa_swagger_ui::serve(&tail, conf.into()).map_err(|err| { 45 | HttpError { 46 | msg: format!("Error serving Swagger UI: {}", err), 47 | status: http::StatusCode::INTERNAL_SERVER_ERROR, 48 | } 49 | })? { 50 | None => Err(HttpError { 51 | status: http::StatusCode::NOT_FOUND, 52 | msg: format!("path not found: {}", tail), 53 | }), 54 | Some(file) => Ok({ 55 | let bytes = Bytes::from(file.bytes.to_vec()); 56 | web::HttpResponse::Ok() 57 | .content_type(file.content_type) 58 | .body(bytes) 59 | }), 60 | } 61 | } 62 | 63 | pub fn ntex_config(config: &mut web::ServiceConfig) { 64 | let swagger_config = Arc::new( 65 | utoipa_swagger_ui::Config::new(["/explorer/swagger.json"]) 66 | .use_base_layout(), 67 | ); 68 | config.service( 69 | web::scope("/explorer/") 70 | .state(swagger_config) 71 | .service(get_swagger), 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /src/services/todo.rs: -------------------------------------------------------------------------------- 1 | use ntex::web; 2 | 3 | use crate::models::todo::TodoPartial; 4 | 5 | /// List all todos 6 | #[utoipa::path( 7 | get, 8 | path = "/todos", 9 | responses( 10 | (status = 200, description = "List of Todo", body = [Todo]), 11 | ), 12 | )] 13 | #[web::get("/todos")] 14 | pub async fn get_todos() -> web::HttpResponse { 15 | web::HttpResponse::Ok().finish() 16 | } 17 | 18 | /// Create a new todo 19 | #[utoipa::path( 20 | post, 21 | path = "/todos", 22 | request_body = TodoPartial, 23 | responses( 24 | (status = 201, description = "Todo created", body = Todo), 25 | ), 26 | )] 27 | #[web::post("/todos")] 28 | pub async fn create_todo( 29 | _todo: web::types::Json, 30 | ) -> web::HttpResponse { 31 | web::HttpResponse::Created().finish() 32 | } 33 | 34 | /// Get a todo by id 35 | #[utoipa::path( 36 | get, 37 | path = "/todos/{id}", 38 | responses( 39 | (status = 200, description = "Todo found", body = Todo), 40 | (status = 404, description = "Todo not found", body = HttpError), 41 | ), 42 | )] 43 | #[web::get("/todos/{id}")] 44 | pub async fn get_todo() -> web::HttpResponse { 45 | web::HttpResponse::Ok().finish() 46 | } 47 | 48 | /// Update a todo by id 49 | #[utoipa::path( 50 | put, 51 | path = "/todos/{id}", 52 | request_body = TodoPartial, 53 | responses( 54 | (status = 200, description = "Todo updated", body = Todo), 55 | (status = 404, description = "Todo not found", body = HttpError), 56 | ), 57 | )] 58 | #[web::put("/todos/{id}")] 59 | pub async fn update_todo() -> web::HttpResponse { 60 | web::HttpResponse::Ok().finish() 61 | } 62 | 63 | /// Delete a todo by id 64 | #[utoipa::path( 65 | delete, 66 | path = "/todos/{id}", 67 | responses( 68 | (status = 200, description = "Todo deleted", body = Todo), 69 | (status = 404, description = "Todo not found", body = HttpError), 70 | ), 71 | )] 72 | #[web::delete("/todos/{id}")] 73 | pub async fn delete_todo() -> web::HttpResponse { 74 | web::HttpResponse::Ok().finish() 75 | } 76 | 77 | pub fn ntex_config(cfg: &mut web::ServiceConfig) { 78 | cfg.service(get_todos); 79 | cfg.service(create_todo); 80 | cfg.service(get_todo); 81 | cfg.service(update_todo); 82 | cfg.service(delete_todo); 83 | } 84 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![background](/background.png) 2 | 3 | Hey, today I wanted to share my knowledge on how to write a Rest API in Rust. It may be easier than you think! 4 | We won't showcase database connectivity in this article. Instead, we focused on demonstrating how to generate `OpenAPI` specifications and serve a `Swagger UI`. 5 | 6 | You can find the full code source on [github](https://github.com/leon3s/ntex-rest-api-example). 7 | 8 | Before starting, make sure you have [Rust](https://www.rust-lang.org) installed. 9 | 10 | Let's start by initializing a new project using `cargo init`. 11 | 12 | ```sh 13 | cargo init my-rest-api 14 | cd my-rest-api 15 | ``` 16 | 17 | That should produce the following directory structure: 18 | 19 | ```console 20 | ├── Cargo.toml 21 | └── src 22 | └── main.rs 23 | ``` 24 | 25 | You can use `rustfmt` for formatting. To do so, create a `rustfmt.toml` file with the following content: 26 | 27 | ```toml 28 | indent_style = "Block" 29 | max_width = 80 30 | tab_spaces = 2 31 | reorder_imports = false 32 | reorder_modules = false 33 | force_multiline_blocks = true 34 | brace_style = "PreferSameLine" 35 | control_brace_style = "AlwaysSameLine" 36 | ``` 37 | 38 | I personally use VSCode. Optionally, you can add this configuration in your `.vscode/settings.json`: 39 | 40 | ```json 41 | { 42 | "editor.rulers": [80], 43 | "editor.tabSize": 2, 44 | "editor.detectIndentation": false, 45 | "editor.trimAutoWhitespace": true, 46 | "editor.formatOnSave": true, 47 | "files.insertFinalNewline": true, 48 | "files.trimTrailingWhitespace": true, 49 | "rust-analyzer.showUnlinkedFileNotification": false, 50 | "rust-analyzer.checkOnSave": true, 51 | "rust-analyzer.check.command": "clippy" 52 | } 53 | ``` 54 | 55 | Your new directory structure should look like this: 56 | 57 | ```console 58 | ├── .gitignore 59 | ├── .vscode 60 | │   └── settings.json 61 | ├── Cargo.lock 62 | ├── Cargo.toml 63 | ├── rustfmt.toml 64 | └── src 65 | └── main.rs 66 | ``` 67 | 68 | We are going to use [ntex](https://ntex.rs) as our HTTP framework.
69 | We can install Rust dependencies by running `cargo add`.
70 | Note that when using `ntex`, we have the ability to choose our `runtime`.
71 | To quickly summarize, the `runtime` will manage your `async|await` pattern.
72 | If you are familiar with the `nodejs runtime`, it's kind of similar in usage. 73 | 74 | For this tutorial, we are going to use tokio as it seems to be the more popular choice. Let's add ntex as a dependency: 75 | 76 | ```sh 77 | cargo add ntex --features tokio 78 | ``` 79 | 80 | Then we are going to update our `main.rs` file with the following content: 81 | 82 | ```rust 83 | use ntex::web; 84 | 85 | #[web::get("/")] 86 | async fn index() -> &'static str { 87 | "Hello world!" 88 | } 89 | 90 | #[ntex::main] 91 | async fn main() -> std::io::Result<()> { 92 | web::server(|| web::App::new().service(index)) 93 | .bind(("0.0.0.0", 8080))? 94 | .run() 95 | .await?; 96 | Ok(()) 97 | } 98 | ``` 99 | 100 | We can run our project by using the following command: 101 | 102 | ```sh 103 | cargo run 104 | ``` 105 | 106 | This command will compile our code and run it.
107 | You should see the following output: 108 | 109 | ```console 110 | Finished dev [unoptimized + debuginfo] target(s) in 17.38s 111 | Running `target/debug/my-rest-api` 112 | ``` 113 | 114 | We can test our server using curl: 115 | 116 | ``` 117 | curl -v localhost:8080 118 | ``` 119 | 120 | ```console 121 | * Trying 127.0.0.1:8080... 122 | * TCP_NODELAY set 123 | * Connected to localhost (127.0.0.1) port 8080 (#0) 124 | > GET / HTTP/1.1 125 | > Host: localhost:8080 126 | > User-Agent: curl/7.68.0 127 | > Accept: */* 128 | > 129 | * Mark bundle as not supporting multiuse 130 | < HTTP/1.1 200 OK 131 | < content-length: 12 132 | < content-type: text/plain; charset=utf-8 133 | < date: Fri, 26 May 2023 11:43:01 GMT 134 | < 135 | * Connection #0 to host localhost left intact 136 | Hello world!% 137 | ``` 138 | 139 | Congratulations! You now have your first HTTP server in `Rust`! 140 | 141 | Now let's create our first `REST endpoints`. 142 | 143 | Regarding the directory architecture, it's up to personal preference. In `ntex`, we use the `.service()` method to add new `endpoints`. Therefore, I have chosen to create a directory called `services` to house my endpoints. 144 | 145 | Let's create the directory: 146 | 147 | ```sh 148 | mkdir src/services 149 | touch src/services/mod.rs 150 | ``` 151 | 152 | Note that by default, `Rust` tries to import a `mod.rs` file from our directories. 153 | 154 | Let's create our default `endpoints` inside `services/mod.rs`: 155 | 156 | ```rust 157 | use ntex::web; 158 | 159 | pub async fn default() -> web::HttpResponse { 160 | web::HttpResponse::NotFound().finish() 161 | } 162 | ``` 163 | 164 | Now we need to indicate that we want to use this module in our main.rs: 165 | 166 | ```rust 167 | use ntex::web; 168 | 169 | mod services; 170 | 171 | #[ntex::main] 172 | async fn main() -> std::io::Result<()> { 173 | web::server(|| { 174 | web::App::new() 175 | // Default endpoint for unregisterd endpoints 176 | .default_service(web::route().to(services::default) 177 | ) 178 | }) 179 | .bind(("0.0.0.0", 8080))? 180 | .run() 181 | .await?; 182 | Ok(()) 183 | } 184 | ``` 185 | 186 | Now, for any unregistered `endpoints`, we will have a 404 error. 187 | 188 | Before continuing, let's add four dependencies: `serde` and `serde_json` for JSON serialization, and `utoipa` with `utoipa-swagger-ui` to have an `OpenAPI` swagger. 189 | 190 | ```sh 191 | cargo add serde --features derive 192 | cargo add serde_json utoipa utoipa-swagger-ui 193 | ``` 194 | 195 | Next, we are going to create our own `HttpError` type as helpers. Create a file under `src/error.rs` with the following content: 196 | 197 | ```rust 198 | use ntex::web; 199 | use ntex::http; 200 | use utoipa::ToSchema; 201 | use serde::{Serialize, Deserialize}; 202 | 203 | /// An http error response 204 | #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] 205 | pub struct HttpError { 206 | /// The error message 207 | pub msg: String, 208 | /// The http status code, skipped in serialization 209 | #[serde(skip)] 210 | pub status: http::StatusCode, 211 | } 212 | 213 | /// Helper function to display an HttpError 214 | impl std::fmt::Display for HttpError { 215 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 216 | write!(f, "[{}] {}", self.status, self.msg) 217 | } 218 | } 219 | 220 | /// Implement standard error for HttpError 221 | impl std::error::Error for HttpError {} 222 | 223 | /// Helper function to convert an HttpError into a ntex::web::HttpResponse 224 | impl web::WebResponseError for HttpError { 225 | fn error_response(&self, _: &web::HttpRequest) -> web::HttpResponse { 226 | web::HttpResponse::build(self.status).json(&self) 227 | } 228 | } 229 | ``` 230 | 231 | We need to import our error module in our `main.rs` let update it: 232 | 233 | ```rust 234 | use ntex::web; 235 | 236 | mod error; 237 | mod services; 238 | 239 | #[ntex::main] 240 | async fn main() -> std::io::Result<()> { 241 | web::server(|| { 242 | web::App::new() 243 | // Default endpoint for unregisterd endpoints 244 | .default_service(web::route().to(services::default) 245 | ) 246 | }) 247 | .bind(("0.0.0.0", 8080))? 248 | .run() 249 | .await?; 250 | Ok(()) 251 | } 252 | ``` 253 | 254 | I think we are ready to write some example `endpoints`. Let's simulate a todo list and create a new file under `src/services/todo.rs`: 255 | 256 | ```rust 257 | use ntex::web; 258 | 259 | #[web::get("/todos")] 260 | pub async fn get_todos() -> web::HttpResponse { 261 | web::HttpResponse::Ok().finish() 262 | } 263 | 264 | #[web::post("/todos")] 265 | pub async fn create_todo() -> web::HttpResponse { 266 | web::HttpResponse::Created().finish() 267 | } 268 | 269 | #[web::get("/todos/{id}")] 270 | pub async fn get_todo() -> web::HttpResponse { 271 | web::HttpResponse::Ok().finish() 272 | } 273 | 274 | #[web::put("/todos/{id}")] 275 | pub async fn update_todo() -> web::HttpResponse { 276 | web::HttpResponse::Ok().finish() 277 | } 278 | 279 | #[web::delete("/todos/{id}")] 280 | pub async fn delete_todo() -> web::HttpResponse { 281 | web::HttpResponse::Ok().finish() 282 | } 283 | 284 | pub fn ntex_config(cfg: &mut web::ServiceConfig) { 285 | cfg.service(get_todos); 286 | cfg.service(create_todo); 287 | cfg.service(get_todo); 288 | cfg.service(update_todo); 289 | cfg.service(delete_todo); 290 | } 291 | ``` 292 | 293 | We need to update our `src/services/mod.rs` to import our `todo.rs`: 294 | 295 | ```rust 296 | pub mod todo; 297 | 298 | use ntex::web; 299 | 300 | pub async fn default() -> web::HttpResponse { 301 | web::HttpResponse::NotFound().finish() 302 | } 303 | ``` 304 | 305 | In our `main.rs`: 306 | 307 | ```rust 308 | use ntex::web; 309 | 310 | mod error; 311 | mod services; 312 | 313 | #[ntex::main] 314 | async fn main() -> std::io::Result<()> { 315 | web::server(|| { 316 | web::App::new() 317 | // Register todo endpoints 318 | .configure(services::todo::ntex_config) 319 | // Default endpoint for unregisterd endpoints 320 | .default_service(web::route().to(services::default)) 321 | }) 322 | .bind(("0.0.0.0", 8080))? 323 | .run() 324 | .await?; 325 | Ok(()) 326 | } 327 | ``` 328 | 329 | Let's create some data structure for our `Todo`. 330 | We are going to create a new directory `src/models` with his `mod.rs` and a `todo.rs` 331 | 332 | ```sh 333 | mkdir src/models 334 | touch src/models/mod.rs 335 | touch src/models/todo.rs 336 | ``` 337 | 338 | In our `src/models/mod.rs` we are going to import `todo.rs`: 339 | 340 | ```rust 341 | pub mod todo; 342 | ``` 343 | 344 | And inside `src/models/todo.rs`, we are going to add some `data structure`: 345 | 346 | ```rust 347 | use utoipa::ToSchema; 348 | use serde::{Serialize, Deserialize}; 349 | 350 | /// Todo model 351 | #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] 352 | pub struct Todo { 353 | /// The todo id 354 | pub id: i32, 355 | /// The todo title 356 | pub title: String, 357 | /// The todo completed status 358 | pub completed: bool, 359 | } 360 | 361 | /// Partial Todo model 362 | #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] 363 | pub struct TodoPartial { 364 | /// The todo title 365 | pub title: String, 366 | } 367 | ``` 368 | 369 | You may notice that we use the `serde` and `utoipa` derive macros to enable `JSON` serialization and conversion to `OpenAPI Schema`. 370 | 371 | Don't forget to update your `main.rs` to import our `models`: 372 | 373 | ```rust 374 | use ntex::web; 375 | 376 | mod error; 377 | mod models; 378 | mod services; 379 | 380 | #[ntex::main] 381 | async fn main() -> std::io::Result<()> { 382 | web::server(|| { 383 | web::App::new() 384 | // Register todo endpoints 385 | .configure(services::todo::ntex_config) 386 | // Default endpoint for unregisterd endpoints 387 | .default_service(web::route().to(services::default)) 388 | }) 389 | .bind(("0.0.0.0", 8080))? 390 | .run() 391 | .await?; 392 | Ok(()) 393 | } 394 | ``` 395 | 396 | With the models in place, we can now generate type-safe endpoints with their documentation. Let's update our `endpoints` inside `src/services/todo.rs`: 397 | 398 | ```rust 399 | use ntex::web; 400 | 401 | use crate::models::todo::TodoPartial; 402 | 403 | /// List all todos 404 | #[utoipa::path( 405 | get, 406 | path = "/todos", 407 | responses( 408 | (status = 200, description = "List of Todo", body = [Todo]), 409 | ), 410 | )] 411 | #[web::get("/todos")] 412 | pub async fn get_todos() -> web::HttpResponse { 413 | web::HttpResponse::Ok().finish() 414 | } 415 | 416 | /// Create a new todo 417 | #[utoipa::path( 418 | post, 419 | path = "/todos", 420 | request_body = TodoPartial, 421 | responses( 422 | (status = 201, description = "Todo created", body = Todo), 423 | ), 424 | )] 425 | #[web::post("/todos")] 426 | pub async fn create_todo( 427 | _todo: web::types::Json, 428 | ) -> web::HttpResponse { 429 | web::HttpResponse::Created().finish() 430 | } 431 | 432 | /// Get a todo by id 433 | #[utoipa::path( 434 | get, 435 | path = "/todos/{id}", 436 | responses( 437 | (status = 200, description = "Todo found", body = Todo), 438 | (status = 404, description = "Todo not found", body = HttpError), 439 | ), 440 | )] 441 | #[web::get("/todos/{id}")] 442 | pub async fn get_todo() -> web::HttpResponse { 443 | web::HttpResponse::Ok().finish() 444 | } 445 | 446 | /// Update a todo by id 447 | #[utoipa::path( 448 | put, 449 | path = "/todos/{id}", 450 | request_body = TodoPartial, 451 | responses( 452 | (status = 200, description = "Todo updated", body = Todo), 453 | (status = 404, description = "Todo not found", body = HttpError), 454 | ), 455 | )] 456 | #[web::put("/todos/{id}")] 457 | pub async fn update_todo() -> web::HttpResponse { 458 | web::HttpResponse::Ok().finish() 459 | } 460 | 461 | /// Delete a todo by id 462 | #[utoipa::path( 463 | delete, 464 | path = "/todos/{id}", 465 | responses( 466 | (status = 200, description = "Todo deleted", body = Todo), 467 | (status = 404, description = "Todo not found", body = HttpError), 468 | ), 469 | )] 470 | #[web::delete("/todos/{id}")] 471 | pub async fn delete_todo() -> web::HttpResponse { 472 | web::HttpResponse::Ok().finish() 473 | } 474 | 475 | pub fn ntex_config(cfg: &mut web::ServiceConfig) { 476 | cfg.service(get_todos); 477 | cfg.service(create_todo); 478 | cfg.service(get_todo); 479 | cfg.service(update_todo); 480 | cfg.service(delete_todo); 481 | } 482 | ``` 483 | 484 | With utoipa, we will be able to serve our Swagger documentation. 485 | 486 | Let's create a new file under `src/services/openapi.rs`: 487 | 488 | ```rust 489 | use std::sync::Arc; 490 | 491 | use ntex::web; 492 | use ntex::http; 493 | use ntex::util::Bytes; 494 | use utoipa::OpenApi; 495 | 496 | use crate::error::HttpError; 497 | use crate::models::todo::{Todo, TodoPartial}; 498 | 499 | use super::todo; 500 | 501 | /// Main structure to generate OpenAPI documentation 502 | #[derive(OpenApi)] 503 | #[openapi( 504 | paths( 505 | todo::get_todos, 506 | todo::create_todo, 507 | todo::get_todo, 508 | todo::update_todo, 509 | todo::delete_todo, 510 | ), 511 | components(schemas(Todo, TodoPartial, HttpError)) 512 | )] 513 | pub(crate) struct ApiDoc; 514 | 515 | #[web::get("/{tail}*")] 516 | async fn get_swagger( 517 | tail: web::types::Path, 518 | openapi_conf: web::types::State>>, 519 | ) -> Result { 520 | if tail.as_ref() == "swagger.json" { 521 | let spec = ApiDoc::openapi().to_json().map_err(|err| HttpError { 522 | status: http::StatusCode::INTERNAL_SERVER_ERROR, 523 | msg: format!("Error generating OpenAPI spec: {}", err), 524 | })?; 525 | return Ok( 526 | web::HttpResponse::Ok() 527 | .content_type("application/json") 528 | .body(spec), 529 | ); 530 | } 531 | let conf = openapi_conf.as_ref().clone(); 532 | match utoipa_swagger_ui::serve(&tail, conf.into()).map_err(|err| { 533 | HttpError { 534 | msg: format!("Error serving Swagger UI: {}", err), 535 | status: http::StatusCode::INTERNAL_SERVER_ERROR, 536 | } 537 | })? { 538 | None => Err(HttpError { 539 | status: http::StatusCode::NOT_FOUND, 540 | msg: format!("path not found: {}", tail), 541 | }), 542 | Some(file) => Ok({ 543 | let bytes = Bytes::from(file.bytes.to_vec()); 544 | web::HttpResponse::Ok() 545 | .content_type(file.content_type) 546 | .body(bytes) 547 | }), 548 | } 549 | } 550 | 551 | pub fn ntex_config(config: &mut web::ServiceConfig) { 552 | let swagger_config = Arc::new( 553 | utoipa_swagger_ui::Config::new(["/explorer/swagger.json"]) 554 | .use_base_layout(), 555 | ); 556 | config.service( 557 | web::scope("/explorer/") 558 | .state(swagger_config) 559 | .service(get_swagger), 560 | ); 561 | } 562 | ``` 563 | 564 | Don't forget to update `src/services/mod.rs` to import `src/services/openapi.rs`: 565 | 566 | ```rust 567 | pub mod todo; 568 | pub mod openapi; 569 | 570 | use ntex::web; 571 | 572 | pub async fn default() -> web::HttpResponse { 573 | web::HttpResponse::NotFound().finish() 574 | } 575 | ``` 576 | 577 | Then we can update our `main.rs` to register our explorer endpoints: 578 | 579 | ```rust 580 | use ntex::web; 581 | 582 | mod error; 583 | mod models; 584 | mod services; 585 | 586 | #[ntex::main] 587 | async fn main() -> std::io::Result<()> { 588 | web::server(|| { 589 | web::App::new() 590 | // Register swagger endpoints 591 | .configure(services::openapi::ntex_config) 592 | // Register todo endpoints 593 | .configure(services::todo::ntex_config) 594 | // Default endpoint for unregisterd endpoints 595 | .default_service(web::route().to(services::default)) 596 | }) 597 | .bind(("0.0.0.0", 8080))? 598 | .run() 599 | .await?; 600 | Ok(()) 601 | } 602 | ``` 603 | 604 | We are good to go. Let's run our server: 605 | 606 | ```sh 607 | cargo run 608 | ``` 609 | 610 | Then we should be able to access to our [explorer](http://localhost:8080/explorer/) on [http://localhost:8080/explorer/](http://localhost:8080/explorer/) 611 | 612 | ![swagger](/swagger.png) 613 | 614 | I Hope you will try to write your next REST API in Rust ! 615 | 616 | Don't forget to take a look at the dependencies documentation: 617 | 618 | - [ntex](https://ntex.rs) 619 | - [serde](https://serde.rs) 620 | - [serde_json](https://github.com/serde-rs/json) 621 | - [utoipa](https://github.com/juhaku/utoipa) 622 | 623 | ## Bonus 624 | 625 | Create a production docker image ! 626 | Add a `Dockerfile` in your project directory with the following content: 627 | 628 | ```Dockerfile 629 | # Builder 630 | FROM rust:1.69.0-alpine3.17 as builder 631 | 632 | WORKDIR /app 633 | 634 | ## Install build dependencies 635 | RUN apk add alpine-sdk musl-dev build-base upx 636 | 637 | ## Copy source code 638 | COPY Cargo.toml Cargo.lock ./ 639 | COPY src ./src 640 | 641 | ## Build release binary 642 | RUN cargo build --release --target x86_64-unknown-linux-musl 643 | ## Pack release binary with UPX (optional) 644 | RUN upx --best --lzma /app/target/x86_64-unknown-linux-musl/release/my-rest-api 645 | 646 | # Runtime 647 | FROM scratch 648 | 649 | ## Copy release binary from builder 650 | COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/my-rest-api /app 651 | 652 | ENTRYPOINT ["/app"] 653 | ``` 654 | 655 | Optionally you can add this `release` profile in your `Cargo.toml`: 656 | 657 | ```toml 658 | [profile.release] 659 | opt-level = "z" 660 | codegen-units = 1 661 | strip = true 662 | lto = true 663 | ``` 664 | 665 | This will optimise the release binary to be as small as possible. Additionally with [upx](https://upx.github.io/) we can create really small docker image ! 666 | 667 | Build your image: 668 | 669 | ```sh 670 | docker build -t my-rest-api:0.0.1 -f Dockerfile . 671 | ``` 672 | 673 | ![docker_image_ls](/docker_image_ls.png) 674 | 675 | If you want to see a more real world usecase i invite you to take a look at my opensource project [Nanocl](https://github.com/nxthat/nanocl). That try to simplify the development and deployment of micro services, with containers or virtual machines ! 676 | 677 | Happy coding ! 678 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "1.0.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "async-channel" 22 | version = "1.8.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" 25 | dependencies = [ 26 | "concurrent-queue", 27 | "event-listener", 28 | "futures-core", 29 | ] 30 | 31 | [[package]] 32 | name = "async-oneshot" 33 | version = "0.5.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "1ec7c75bcbcb0139e9177f30692fd617405ca4e0c27802e128d53171f7042e2c" 36 | dependencies = [ 37 | "futures-micro", 38 | ] 39 | 40 | [[package]] 41 | name = "autocfg" 42 | version = "1.1.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 45 | 46 | [[package]] 47 | name = "base64" 48 | version = "0.21.2" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" 51 | 52 | [[package]] 53 | name = "bitflags" 54 | version = "1.3.2" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 57 | 58 | [[package]] 59 | name = "block-buffer" 60 | version = "0.10.4" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 63 | dependencies = [ 64 | "generic-array", 65 | ] 66 | 67 | [[package]] 68 | name = "byteorder" 69 | version = "1.4.3" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 72 | 73 | [[package]] 74 | name = "bytes" 75 | version = "1.4.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 78 | 79 | [[package]] 80 | name = "cfg-if" 81 | version = "1.0.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 84 | 85 | [[package]] 86 | name = "concurrent-queue" 87 | version = "2.2.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" 90 | dependencies = [ 91 | "crossbeam-utils", 92 | ] 93 | 94 | [[package]] 95 | name = "cpufeatures" 96 | version = "0.2.7" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" 99 | dependencies = [ 100 | "libc", 101 | ] 102 | 103 | [[package]] 104 | name = "crc32fast" 105 | version = "1.3.2" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 108 | dependencies = [ 109 | "cfg-if", 110 | ] 111 | 112 | [[package]] 113 | name = "crossbeam-utils" 114 | version = "0.8.15" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" 117 | dependencies = [ 118 | "cfg-if", 119 | ] 120 | 121 | [[package]] 122 | name = "crypto-common" 123 | version = "0.1.6" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 126 | dependencies = [ 127 | "generic-array", 128 | "typenum", 129 | ] 130 | 131 | [[package]] 132 | name = "digest" 133 | version = "0.10.7" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 136 | dependencies = [ 137 | "block-buffer", 138 | "crypto-common", 139 | ] 140 | 141 | [[package]] 142 | name = "dirs" 143 | version = "4.0.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 146 | dependencies = [ 147 | "dirs-sys", 148 | ] 149 | 150 | [[package]] 151 | name = "dirs-sys" 152 | version = "0.3.7" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 155 | dependencies = [ 156 | "libc", 157 | "redox_users", 158 | "winapi", 159 | ] 160 | 161 | [[package]] 162 | name = "encoding_rs" 163 | version = "0.8.32" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" 166 | dependencies = [ 167 | "cfg-if", 168 | ] 169 | 170 | [[package]] 171 | name = "event-listener" 172 | version = "2.5.3" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 175 | 176 | [[package]] 177 | name = "flate2" 178 | version = "1.0.26" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" 181 | dependencies = [ 182 | "crc32fast", 183 | "miniz_oxide", 184 | ] 185 | 186 | [[package]] 187 | name = "fnv" 188 | version = "1.0.7" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 191 | 192 | [[package]] 193 | name = "form_urlencoded" 194 | version = "1.1.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 197 | dependencies = [ 198 | "percent-encoding", 199 | ] 200 | 201 | [[package]] 202 | name = "futures-core" 203 | version = "0.3.28" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 206 | 207 | [[package]] 208 | name = "futures-micro" 209 | version = "0.5.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "b460264b3593d68b16a7bc35f7bc226ddfebdf9a1c8db1ed95d5cc6b7168c826" 212 | dependencies = [ 213 | "pin-project-lite", 214 | ] 215 | 216 | [[package]] 217 | name = "futures-sink" 218 | version = "0.3.28" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 221 | 222 | [[package]] 223 | name = "futures-timer" 224 | version = "3.0.2" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" 227 | 228 | [[package]] 229 | name = "fxhash" 230 | version = "0.2.1" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 233 | dependencies = [ 234 | "byteorder", 235 | ] 236 | 237 | [[package]] 238 | name = "generic-array" 239 | version = "0.14.7" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 242 | dependencies = [ 243 | "typenum", 244 | "version_check", 245 | ] 246 | 247 | [[package]] 248 | name = "getrandom" 249 | version = "0.2.9" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" 252 | dependencies = [ 253 | "cfg-if", 254 | "libc", 255 | "wasi", 256 | ] 257 | 258 | [[package]] 259 | name = "hashbrown" 260 | version = "0.12.3" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 263 | 264 | [[package]] 265 | name = "hermit-abi" 266 | version = "0.2.6" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 269 | dependencies = [ 270 | "libc", 271 | ] 272 | 273 | [[package]] 274 | name = "http" 275 | version = "0.2.9" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 278 | dependencies = [ 279 | "bytes", 280 | "fnv", 281 | "itoa", 282 | ] 283 | 284 | [[package]] 285 | name = "httparse" 286 | version = "1.8.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 289 | 290 | [[package]] 291 | name = "httpdate" 292 | version = "1.0.2" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 295 | 296 | [[package]] 297 | name = "indexmap" 298 | version = "1.9.3" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 301 | dependencies = [ 302 | "autocfg", 303 | "hashbrown", 304 | "serde", 305 | ] 306 | 307 | [[package]] 308 | name = "itoa" 309 | version = "1.0.6" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 312 | 313 | [[package]] 314 | name = "libc" 315 | version = "0.2.144" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" 318 | 319 | [[package]] 320 | name = "log" 321 | version = "0.4.17" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 324 | dependencies = [ 325 | "cfg-if", 326 | ] 327 | 328 | [[package]] 329 | name = "memchr" 330 | version = "2.5.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 333 | 334 | [[package]] 335 | name = "mime" 336 | version = "0.3.17" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 339 | 340 | [[package]] 341 | name = "mime_guess" 342 | version = "2.0.4" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 345 | dependencies = [ 346 | "mime", 347 | "unicase", 348 | ] 349 | 350 | [[package]] 351 | name = "miniz_oxide" 352 | version = "0.7.1" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 355 | dependencies = [ 356 | "adler", 357 | ] 358 | 359 | [[package]] 360 | name = "mio" 361 | version = "0.8.6" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" 364 | dependencies = [ 365 | "libc", 366 | "log", 367 | "wasi", 368 | "windows-sys 0.45.0", 369 | ] 370 | 371 | [[package]] 372 | name = "my-rest-api" 373 | version = "0.1.0" 374 | dependencies = [ 375 | "ntex", 376 | "serde", 377 | "serde_json", 378 | "utoipa", 379 | "utoipa-swagger-ui", 380 | ] 381 | 382 | [[package]] 383 | name = "nanorand" 384 | version = "0.7.0" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 387 | 388 | [[package]] 389 | name = "ntex" 390 | version = "0.6.7" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "5807f908e7d45a81792e8d790d2457ca5faaf837b75c7aeee72ab0fd51ff8b74" 393 | dependencies = [ 394 | "async-channel", 395 | "async-oneshot", 396 | "base64", 397 | "bitflags", 398 | "encoding_rs", 399 | "httparse", 400 | "httpdate", 401 | "log", 402 | "mime", 403 | "nanorand", 404 | "ntex-bytes", 405 | "ntex-codec", 406 | "ntex-connect", 407 | "ntex-h2", 408 | "ntex-http", 409 | "ntex-io", 410 | "ntex-macros", 411 | "ntex-router", 412 | "ntex-rt", 413 | "ntex-service", 414 | "ntex-tls", 415 | "ntex-tokio", 416 | "ntex-util", 417 | "num_cpus", 418 | "percent-encoding", 419 | "pin-project-lite", 420 | "polling", 421 | "regex", 422 | "serde", 423 | "serde_json", 424 | "serde_urlencoded", 425 | "sha-1", 426 | "socket2 0.5.3", 427 | "thiserror", 428 | ] 429 | 430 | [[package]] 431 | name = "ntex-bytes" 432 | version = "0.1.19" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "f5ab7c2ea2a2cf00edb6c307c625f1a037de4e0bc2863127b4b3a73af348f24e" 435 | dependencies = [ 436 | "bitflags", 437 | "bytes", 438 | "futures-core", 439 | "serde", 440 | ] 441 | 442 | [[package]] 443 | name = "ntex-codec" 444 | version = "0.6.2" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "69a7e111d946bb915d712df496728ca2a120b1b5643f66c580f13023bce46fda" 447 | dependencies = [ 448 | "ntex-bytes", 449 | ] 450 | 451 | [[package]] 452 | name = "ntex-connect" 453 | version = "0.2.1" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "7ebeb43837a991a064a929e5bc3616abe6ceac1e336d6ced721f0ca94afee5fc" 456 | dependencies = [ 457 | "log", 458 | "ntex-bytes", 459 | "ntex-http", 460 | "ntex-io", 461 | "ntex-rt", 462 | "ntex-service", 463 | "ntex-tls", 464 | "ntex-tokio", 465 | "ntex-util", 466 | "thiserror", 467 | ] 468 | 469 | [[package]] 470 | name = "ntex-h2" 471 | version = "0.2.4" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "c3aa6cfc7de2f5bdd8481a8633876fc5e1656ee95cf89b5f718595d1dfee8315" 474 | dependencies = [ 475 | "bitflags", 476 | "fxhash", 477 | "log", 478 | "ntex-bytes", 479 | "ntex-codec", 480 | "ntex-connect", 481 | "ntex-http", 482 | "ntex-io", 483 | "ntex-rt", 484 | "ntex-service", 485 | "ntex-util", 486 | "pin-project-lite", 487 | "thiserror", 488 | ] 489 | 490 | [[package]] 491 | name = "ntex-http" 492 | version = "0.1.9" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "bbcb740f9033d868fb8669c9d8adeaf68675acfc2924684a479c1e98a5c040dc" 495 | dependencies = [ 496 | "fxhash", 497 | "http", 498 | "itoa", 499 | "log", 500 | "ntex-bytes", 501 | ] 502 | 503 | [[package]] 504 | name = "ntex-io" 505 | version = "0.2.10" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "04bb5b5e2c48c9cfa9cc158734194cb4811ce6c56d4e0366c7708e4e5ef7e7ad" 508 | dependencies = [ 509 | "bitflags", 510 | "log", 511 | "ntex-bytes", 512 | "ntex-codec", 513 | "ntex-service", 514 | "ntex-util", 515 | "pin-project-lite", 516 | ] 517 | 518 | [[package]] 519 | name = "ntex-macros" 520 | version = "0.1.3" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "50a359f2a10c712b0446675070c22b1437d57a7cf08139f6a229e1e80817ed84" 523 | dependencies = [ 524 | "proc-macro2", 525 | "quote", 526 | "syn 1.0.109", 527 | ] 528 | 529 | [[package]] 530 | name = "ntex-router" 531 | version = "0.5.1" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "67631bca69fc552ba9615b36637eee827fbf4752eec4c7bc10f7135695cc1b9e" 534 | dependencies = [ 535 | "http", 536 | "log", 537 | "ntex-bytes", 538 | "regex", 539 | "serde", 540 | ] 541 | 542 | [[package]] 543 | name = "ntex-rt" 544 | version = "0.4.9" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "04f780c84494da078bdfa1567964a2fb3ca044859a2af61e01b3ee6892a8836a" 547 | dependencies = [ 548 | "async-channel", 549 | "async-oneshot", 550 | "futures-core", 551 | "log", 552 | "tokio", 553 | ] 554 | 555 | [[package]] 556 | name = "ntex-service" 557 | version = "1.0.2" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "caf207fde771c30613f12d25050c2df2847fe6042f9b9b25180d81fc192e51c4" 560 | dependencies = [ 561 | "pin-project-lite", 562 | ] 563 | 564 | [[package]] 565 | name = "ntex-tls" 566 | version = "0.2.4" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "cdaa45e310ecc5110d0c5f1f4fcc181ed78c9b0db110f0f75eabc003ff9c2aeb" 569 | dependencies = [ 570 | "log", 571 | "ntex-bytes", 572 | "ntex-io", 573 | "ntex-service", 574 | "ntex-util", 575 | "pin-project-lite", 576 | ] 577 | 578 | [[package]] 579 | name = "ntex-tokio" 580 | version = "0.2.3" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "e996405b96658c8dce71fb1006f72dd967f55856d04a62b9e0cf5099c6dbfdb5" 583 | dependencies = [ 584 | "log", 585 | "ntex-bytes", 586 | "ntex-io", 587 | "ntex-util", 588 | "pin-project-lite", 589 | "tokio", 590 | ] 591 | 592 | [[package]] 593 | name = "ntex-util" 594 | version = "0.2.2" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "7670b16cc873dae1cbc962c571786e9c92c26662c37800c06333e6195493f474" 597 | dependencies = [ 598 | "bitflags", 599 | "futures-core", 600 | "futures-sink", 601 | "futures-timer", 602 | "fxhash", 603 | "log", 604 | "ntex-rt", 605 | "ntex-service", 606 | "pin-project-lite", 607 | "slab", 608 | ] 609 | 610 | [[package]] 611 | name = "num_cpus" 612 | version = "1.15.0" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 615 | dependencies = [ 616 | "hermit-abi", 617 | "libc", 618 | ] 619 | 620 | [[package]] 621 | name = "percent-encoding" 622 | version = "2.2.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 625 | 626 | [[package]] 627 | name = "pin-project-lite" 628 | version = "0.2.9" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 631 | 632 | [[package]] 633 | name = "polling" 634 | version = "2.8.0" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" 637 | dependencies = [ 638 | "autocfg", 639 | "bitflags", 640 | "cfg-if", 641 | "concurrent-queue", 642 | "libc", 643 | "log", 644 | "pin-project-lite", 645 | "windows-sys 0.48.0", 646 | ] 647 | 648 | [[package]] 649 | name = "proc-macro-error" 650 | version = "1.0.4" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 653 | dependencies = [ 654 | "proc-macro-error-attr", 655 | "proc-macro2", 656 | "quote", 657 | "syn 1.0.109", 658 | "version_check", 659 | ] 660 | 661 | [[package]] 662 | name = "proc-macro-error-attr" 663 | version = "1.0.4" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 666 | dependencies = [ 667 | "proc-macro2", 668 | "quote", 669 | "version_check", 670 | ] 671 | 672 | [[package]] 673 | name = "proc-macro2" 674 | version = "1.0.59" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" 677 | dependencies = [ 678 | "unicode-ident", 679 | ] 680 | 681 | [[package]] 682 | name = "quote" 683 | version = "1.0.28" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 686 | dependencies = [ 687 | "proc-macro2", 688 | ] 689 | 690 | [[package]] 691 | name = "redox_syscall" 692 | version = "0.2.16" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 695 | dependencies = [ 696 | "bitflags", 697 | ] 698 | 699 | [[package]] 700 | name = "redox_users" 701 | version = "0.4.3" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 704 | dependencies = [ 705 | "getrandom", 706 | "redox_syscall", 707 | "thiserror", 708 | ] 709 | 710 | [[package]] 711 | name = "regex" 712 | version = "1.8.3" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" 715 | dependencies = [ 716 | "aho-corasick", 717 | "memchr", 718 | "regex-syntax", 719 | ] 720 | 721 | [[package]] 722 | name = "regex-syntax" 723 | version = "0.7.2" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" 726 | 727 | [[package]] 728 | name = "rust-embed" 729 | version = "6.6.1" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" 732 | dependencies = [ 733 | "rust-embed-impl", 734 | "rust-embed-utils", 735 | "walkdir", 736 | ] 737 | 738 | [[package]] 739 | name = "rust-embed-impl" 740 | version = "6.5.0" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" 743 | dependencies = [ 744 | "proc-macro2", 745 | "quote", 746 | "rust-embed-utils", 747 | "shellexpand", 748 | "syn 1.0.109", 749 | "walkdir", 750 | ] 751 | 752 | [[package]] 753 | name = "rust-embed-utils" 754 | version = "7.5.0" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" 757 | dependencies = [ 758 | "sha2", 759 | "walkdir", 760 | ] 761 | 762 | [[package]] 763 | name = "ryu" 764 | version = "1.0.13" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 767 | 768 | [[package]] 769 | name = "same-file" 770 | version = "1.0.6" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 773 | dependencies = [ 774 | "winapi-util", 775 | ] 776 | 777 | [[package]] 778 | name = "serde" 779 | version = "1.0.163" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" 782 | dependencies = [ 783 | "serde_derive", 784 | ] 785 | 786 | [[package]] 787 | name = "serde_derive" 788 | version = "1.0.163" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" 791 | dependencies = [ 792 | "proc-macro2", 793 | "quote", 794 | "syn 2.0.17", 795 | ] 796 | 797 | [[package]] 798 | name = "serde_json" 799 | version = "1.0.96" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" 802 | dependencies = [ 803 | "itoa", 804 | "ryu", 805 | "serde", 806 | ] 807 | 808 | [[package]] 809 | name = "serde_urlencoded" 810 | version = "0.7.1" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 813 | dependencies = [ 814 | "form_urlencoded", 815 | "itoa", 816 | "ryu", 817 | "serde", 818 | ] 819 | 820 | [[package]] 821 | name = "sha-1" 822 | version = "0.10.1" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" 825 | dependencies = [ 826 | "cfg-if", 827 | "cpufeatures", 828 | "digest", 829 | ] 830 | 831 | [[package]] 832 | name = "sha2" 833 | version = "0.10.6" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 836 | dependencies = [ 837 | "cfg-if", 838 | "cpufeatures", 839 | "digest", 840 | ] 841 | 842 | [[package]] 843 | name = "shellexpand" 844 | version = "2.1.2" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" 847 | dependencies = [ 848 | "dirs", 849 | ] 850 | 851 | [[package]] 852 | name = "signal-hook-registry" 853 | version = "1.4.1" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 856 | dependencies = [ 857 | "libc", 858 | ] 859 | 860 | [[package]] 861 | name = "slab" 862 | version = "0.4.8" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 865 | dependencies = [ 866 | "autocfg", 867 | ] 868 | 869 | [[package]] 870 | name = "socket2" 871 | version = "0.4.9" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 874 | dependencies = [ 875 | "libc", 876 | "winapi", 877 | ] 878 | 879 | [[package]] 880 | name = "socket2" 881 | version = "0.5.3" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" 884 | dependencies = [ 885 | "libc", 886 | "windows-sys 0.48.0", 887 | ] 888 | 889 | [[package]] 890 | name = "syn" 891 | version = "1.0.109" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 894 | dependencies = [ 895 | "proc-macro2", 896 | "quote", 897 | "unicode-ident", 898 | ] 899 | 900 | [[package]] 901 | name = "syn" 902 | version = "2.0.17" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "45b6ddbb36c5b969c182aec3c4a0bce7df3fbad4b77114706a49aacc80567388" 905 | dependencies = [ 906 | "proc-macro2", 907 | "quote", 908 | "unicode-ident", 909 | ] 910 | 911 | [[package]] 912 | name = "thiserror" 913 | version = "1.0.40" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 916 | dependencies = [ 917 | "thiserror-impl", 918 | ] 919 | 920 | [[package]] 921 | name = "thiserror-impl" 922 | version = "1.0.40" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 925 | dependencies = [ 926 | "proc-macro2", 927 | "quote", 928 | "syn 2.0.17", 929 | ] 930 | 931 | [[package]] 932 | name = "tokio" 933 | version = "1.28.1" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" 936 | dependencies = [ 937 | "autocfg", 938 | "libc", 939 | "mio", 940 | "pin-project-lite", 941 | "signal-hook-registry", 942 | "socket2 0.4.9", 943 | "windows-sys 0.48.0", 944 | ] 945 | 946 | [[package]] 947 | name = "typenum" 948 | version = "1.16.0" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 951 | 952 | [[package]] 953 | name = "unicase" 954 | version = "2.6.0" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 957 | dependencies = [ 958 | "version_check", 959 | ] 960 | 961 | [[package]] 962 | name = "unicode-ident" 963 | version = "1.0.9" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 966 | 967 | [[package]] 968 | name = "utoipa" 969 | version = "3.3.0" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "68ae74ef183fae36d650f063ae7bde1cacbe1cd7e72b617cbe1e985551878b98" 972 | dependencies = [ 973 | "indexmap", 974 | "serde", 975 | "serde_json", 976 | "utoipa-gen", 977 | ] 978 | 979 | [[package]] 980 | name = "utoipa-gen" 981 | version = "3.3.0" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "7ea8ac818da7e746a63285594cce8a96f5e00ee31994e655bd827569cb8b137b" 984 | dependencies = [ 985 | "proc-macro-error", 986 | "proc-macro2", 987 | "quote", 988 | "syn 2.0.17", 989 | ] 990 | 991 | [[package]] 992 | name = "utoipa-swagger-ui" 993 | version = "3.1.3" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "062bba5a3568e126ac72049a63254f4cb1da2eb713db0c1ab2a4c76be191db8c" 996 | dependencies = [ 997 | "mime_guess", 998 | "regex", 999 | "rust-embed", 1000 | "serde", 1001 | "serde_json", 1002 | "utoipa", 1003 | "zip", 1004 | ] 1005 | 1006 | [[package]] 1007 | name = "version_check" 1008 | version = "0.9.4" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1011 | 1012 | [[package]] 1013 | name = "walkdir" 1014 | version = "2.3.3" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" 1017 | dependencies = [ 1018 | "same-file", 1019 | "winapi-util", 1020 | ] 1021 | 1022 | [[package]] 1023 | name = "wasi" 1024 | version = "0.11.0+wasi-snapshot-preview1" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1027 | 1028 | [[package]] 1029 | name = "winapi" 1030 | version = "0.3.9" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1033 | dependencies = [ 1034 | "winapi-i686-pc-windows-gnu", 1035 | "winapi-x86_64-pc-windows-gnu", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "winapi-i686-pc-windows-gnu" 1040 | version = "0.4.0" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1043 | 1044 | [[package]] 1045 | name = "winapi-util" 1046 | version = "0.1.5" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1049 | dependencies = [ 1050 | "winapi", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "winapi-x86_64-pc-windows-gnu" 1055 | version = "0.4.0" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1058 | 1059 | [[package]] 1060 | name = "windows-sys" 1061 | version = "0.45.0" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1064 | dependencies = [ 1065 | "windows-targets 0.42.2", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "windows-sys" 1070 | version = "0.48.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1073 | dependencies = [ 1074 | "windows-targets 0.48.0", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "windows-targets" 1079 | version = "0.42.2" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1082 | dependencies = [ 1083 | "windows_aarch64_gnullvm 0.42.2", 1084 | "windows_aarch64_msvc 0.42.2", 1085 | "windows_i686_gnu 0.42.2", 1086 | "windows_i686_msvc 0.42.2", 1087 | "windows_x86_64_gnu 0.42.2", 1088 | "windows_x86_64_gnullvm 0.42.2", 1089 | "windows_x86_64_msvc 0.42.2", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "windows-targets" 1094 | version = "0.48.0" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 1097 | dependencies = [ 1098 | "windows_aarch64_gnullvm 0.48.0", 1099 | "windows_aarch64_msvc 0.48.0", 1100 | "windows_i686_gnu 0.48.0", 1101 | "windows_i686_msvc 0.48.0", 1102 | "windows_x86_64_gnu 0.48.0", 1103 | "windows_x86_64_gnullvm 0.48.0", 1104 | "windows_x86_64_msvc 0.48.0", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "windows_aarch64_gnullvm" 1109 | version = "0.42.2" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1112 | 1113 | [[package]] 1114 | name = "windows_aarch64_gnullvm" 1115 | version = "0.48.0" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1118 | 1119 | [[package]] 1120 | name = "windows_aarch64_msvc" 1121 | version = "0.42.2" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1124 | 1125 | [[package]] 1126 | name = "windows_aarch64_msvc" 1127 | version = "0.48.0" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1130 | 1131 | [[package]] 1132 | name = "windows_i686_gnu" 1133 | version = "0.42.2" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1136 | 1137 | [[package]] 1138 | name = "windows_i686_gnu" 1139 | version = "0.48.0" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1142 | 1143 | [[package]] 1144 | name = "windows_i686_msvc" 1145 | version = "0.42.2" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1148 | 1149 | [[package]] 1150 | name = "windows_i686_msvc" 1151 | version = "0.48.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1154 | 1155 | [[package]] 1156 | name = "windows_x86_64_gnu" 1157 | version = "0.42.2" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1160 | 1161 | [[package]] 1162 | name = "windows_x86_64_gnu" 1163 | version = "0.48.0" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1166 | 1167 | [[package]] 1168 | name = "windows_x86_64_gnullvm" 1169 | version = "0.42.2" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1172 | 1173 | [[package]] 1174 | name = "windows_x86_64_gnullvm" 1175 | version = "0.48.0" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1178 | 1179 | [[package]] 1180 | name = "windows_x86_64_msvc" 1181 | version = "0.42.2" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1184 | 1185 | [[package]] 1186 | name = "windows_x86_64_msvc" 1187 | version = "0.48.0" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1190 | 1191 | [[package]] 1192 | name = "zip" 1193 | version = "0.6.6" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" 1196 | dependencies = [ 1197 | "byteorder", 1198 | "crc32fast", 1199 | "crossbeam-utils", 1200 | "flate2", 1201 | ] 1202 | --------------------------------------------------------------------------------