diff --git a/libs/Cargo.toml b/libs/Cargo.toml index 0e788e988..5bc341951 100644 --- a/libs/Cargo.toml +++ b/libs/Cargo.toml @@ -4,6 +4,7 @@ members = [ "blockscout-auth", "blockscout-client/crate", "blockscout-db", + "endpoints/swagger", "blockscout-service-launcher", "display-bytes", "env-collector", diff --git a/libs/endpoints/swagger/Cargo.toml b/libs/endpoints/swagger/Cargo.toml new file mode 100644 index 000000000..b4face49e --- /dev/null +++ b/libs/endpoints/swagger/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "blockscout-endpoint-swagger" +version = "0.1.0" +description = "Configuration tools for enabling swagger endpoint" +license = "MIT" +repository = "https://github.com/blockscout/blockscout-rs" +keywords = ["blockscout", "service", "http", "microservices", "swagger", "endpoint"] +categories = ["web-programming::http-server"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +actix-web = "4" +actix-files = "0.6.6" diff --git a/libs/endpoints/swagger/src/lib.rs b/libs/endpoints/swagger/src/lib.rs new file mode 100644 index 000000000..233887393 --- /dev/null +++ b/libs/endpoints/swagger/src/lib.rs @@ -0,0 +1,20 @@ +use std::{path::PathBuf, sync::Arc}; + +use actix_files::NamedFile; +use actix_web::{web::get, HttpRequest, Result}; + +async fn serve_swagger_from(path: Arc, _req: HttpRequest) -> Result { + Ok(NamedFile::open(path.as_ref())?) +} + +pub fn route_swagger( + service_config: &mut actix_web::web::ServiceConfig, + swagger_file_path: PathBuf, + route: &str, +) { + let path = Arc::new(swagger_file_path); + let serve_swagger = move |req: HttpRequest| serve_swagger_from(path.clone(), req); + service_config.configure(|config| { + config.route(route, get().to(serve_swagger)); + }); +} diff --git a/stats/Cargo.lock b/stats/Cargo.lock index c43146aca..213a5c19b 100644 --- a/stats/Cargo.lock +++ b/stats/Cargo.lock @@ -34,6 +34,29 @@ dependencies = [ "smallvec", ] +[[package]] +name = "actix-files" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0773d59061dedb49a8aed04c67291b9d8cf2fe0b60130a381aab53c6dd86e9be" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "bitflags 2.5.0", + "bytes", + "derive_more", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "v_htmlescape", +] + [[package]] name = "actix-http" version = "3.7.0" @@ -54,8 +77,8 @@ dependencies = [ "encoding_rs", "flate2", "futures-core", - "h2", - "http", + "h2 0.3.26", + "http 0.2.12", "httparse", "httpdate", "itoa", @@ -90,7 +113,7 @@ source = "git+https://github.com/blockscout/actix-prost#9cc47aa1cb7b63ce1cb81491 dependencies = [ "actix-http", "actix-web", - "http", + "http 0.2.12", "prost 0.11.9", "serde", "serde_json", @@ -132,7 +155,7 @@ checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" dependencies = [ "bytestring", "cfg-if", - "http", + "http 0.2.12", "regex", "regex-lite", "serde", @@ -628,9 +651,9 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "itoa", "matchit", "memchr", @@ -639,7 +662,7 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sync_wrapper", + "sync_wrapper 0.1.2", "tower", "tower-layer", "tower-service", @@ -654,8 +677,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "mime", "rustversion", "tower-layer", @@ -787,6 +810,15 @@ dependencies = [ "sea-orm-migration", ] +[[package]] +name = "blockscout-endpoint-swagger" +version = "0.1.0" +source = "git+https://github.com/blockscout/blockscout-rs?rev=0d01bdf7#0d01bdf79e2798c9f74f9690cc5ac5cbc03b39c6" +dependencies = [ + "actix-files", + "actix-web", +] + [[package]] name = "blockscout-metrics-tools" version = "0.1.0" @@ -811,7 +843,7 @@ dependencies = [ "opentelemetry", "opentelemetry-jaeger", "prometheus", - "reqwest", + "reqwest 0.11.27", "sea-orm", "sea-orm-migration", "serde", @@ -1654,7 +1686,26 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", "indexmap 2.2.6", "slab", "tokio", @@ -1755,6 +1806,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1762,10 +1824,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + [[package]] name = "httparse" version = "1.8.0" @@ -1788,9 +1879,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1802,13 +1893,50 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls 0.23.12", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-timeout" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper", + "hyper 0.14.29", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -1821,12 +1949,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.29", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -2238,6 +2402,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3192,11 +3366,54 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", + "hyper-tls 0.5.0", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls", + "hyper-tls 0.6.0", + "hyper-util", "ipnet", "js-sys", "log", @@ -3205,11 +3422,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 2.1.3", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "system-configuration", "tokio", "tokio-native-tls", @@ -3218,7 +3435,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "winreg 0.52.0", ] [[package]] @@ -3381,10 +3598,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.6", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -3394,6 +3624,22 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -3404,6 +3650,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -3889,8 +4146,8 @@ dependencies = [ "paste", "percent-encoding", "rust_decimal", - "rustls", - "rustls-pemfile", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", "serde", "serde_json", "sha2", @@ -4124,6 +4381,7 @@ dependencies = [ "actix-web", "anyhow", "async-trait", + "blockscout-endpoint-swagger", "blockscout-service-launcher", "bytes", "chrono", @@ -4135,6 +4393,7 @@ dependencies = [ "liquid-json", "paste", "pretty_assertions", + "reqwest 0.12.5", "sea-orm", "serde", "serde_json", @@ -4216,6 +4475,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "system-configuration" version = "0.5.1" @@ -4412,6 +4677,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.12", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -4475,10 +4751,10 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "hyper-timeout", "percent-encoding", "pin-project", @@ -4688,6 +4964,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -4772,6 +5057,12 @@ dependencies = [ "serde", ] +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + [[package]] name = "valuable" version = "0.1.0" @@ -5116,6 +5407,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/stats/stats-server/Cargo.toml b/stats/stats-server/Cargo.toml index 6d393e2ae..2e8362f59 100644 --- a/stats/stats-server/Cargo.toml +++ b/stats/stats-server/Cargo.toml @@ -24,6 +24,7 @@ sea-orm = { version = "0.12", features = [ ] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } blockscout-service-launcher = { workspace = true, features = [ "database-0_12" ] } +blockscout-endpoint-swagger = { git = "https://github.com/blockscout/blockscout-rs", rev = "0d01bdf7" } cron = "0.12" convert_case = "0.6.0" itertools = "0.11.0" @@ -36,3 +37,4 @@ paste = "1.0" stats = { path = "../stats", features = ["test-utils"] } blockscout-service-launcher = { workspace = true, features = [ "database-0_12", "test-server" ] } pretty_assertions = "1.3" +reqwest = "0.12" diff --git a/stats/stats-server/src/server.rs b/stats/stats-server/src/server.rs index ac317e882..85bd6d4f1 100644 --- a/stats/stats-server/src/server.rs +++ b/stats/stats-server/src/server.rs @@ -9,6 +9,7 @@ use crate::{ update_service::UpdateService, }; +use blockscout_endpoint_swagger::route_swagger; use blockscout_service_launcher::launcher::{self, LaunchSettings}; use sea_orm::{ConnectOptions, Database}; use stats_proto::blockscout::stats::v1::{ @@ -28,9 +29,11 @@ struct HttpRouter { impl launcher::HttpRouter for HttpRouter { fn register_routes(&self, service_config: &mut actix_web::web::ServiceConfig) { + let swagger_file = std::path::PathBuf::from("../stats-proto/swagger/stats.swagger.yaml"); service_config .configure(|config| route_health(config, self.health.clone())) - .configure(|config| route_stats_service(config, self.stats.clone())); + .configure(|config| route_stats_service(config, self.stats.clone())) + .configure(|config| route_swagger(config, swagger_file, "/api/v1/docs/swagger.yaml")); } } diff --git a/stats/stats-server/tests/swagger.rs b/stats/stats-server/tests/swagger.rs new file mode 100644 index 000000000..c1eaf4e61 --- /dev/null +++ b/stats/stats-server/tests/swagger.rs @@ -0,0 +1,57 @@ +use blockscout_service_launcher::{ + launcher::ConfigSettings, + test_server::{get_test_server_settings, init_server}, +}; +use pretty_assertions::assert_eq; +use reqwest::{RequestBuilder, Response}; +use stats::tests::init_db::init_db_all; +use stats_server::{stats, Settings}; +use std::{path::PathBuf, str::FromStr}; + +async fn send_arbitrary_request(request: RequestBuilder) -> Response { + let response = request + .send() + .await + .unwrap_or_else(|_| panic!("Failed to send request")); + + if !response.status().is_success() { + let status = response.status(); + let message = response.text().await.expect("Read body as text"); + panic!("Invalid status code (success expected). Status: {status}. Message: {message}") + } + response +} + +#[tokio::test] +#[ignore = "needs database"] +async fn test_swagger_ok() { + let (stats_db, blockscout_db) = init_db_all("test_swagger_ok").await; + std::env::set_var("STATS__CONFIG", "./tests/config/test.toml"); + let mut settings = Settings::build().expect("Failed to build settings"); + let (server_settings, base) = get_test_server_settings(); + settings.server = server_settings; + settings.charts_config = PathBuf::from_str("../config/charts.json").unwrap(); + settings.layout_config = PathBuf::from_str("../config/layout.json").unwrap(); + settings.update_groups_config = PathBuf::from_str("../config/update_groups.json").unwrap(); + settings.db_url = stats_db.db_url(); + settings.blockscout_db_url = blockscout_db.db_url(); + + init_server(|| stats(settings), &base).await; + + // Sleep until server will start and calculate all values + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + + let request = reqwest::Client::new().request( + reqwest::Method::GET, + base.join("/api/v1/docs/swagger.yaml").unwrap(), + ); + let response = send_arbitrary_request(request).await; + let swagger_file_contents = response.text().await.unwrap(); + + let expected_swagger_file_contents = + tokio::fs::read_to_string("../stats-proto/swagger/stats.swagger.yaml") + .await + .unwrap(); + + assert_eq!(swagger_file_contents, expected_swagger_file_contents,); +}