Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: get server for Openapi specification #236

Merged
merged 17 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,6 @@ testcov
testdocs
fmtchk
fmtfix
gethostname
afinet
netifas
55 changes: 55 additions & 0 deletions catalyst-gateway/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions catalyst-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ tokio = "1"

dotenvy = "0.15"

local-ip-address = "0.5.7"
gethostname = "0.4.3"

[workspace.lints.rust]
warnings = "deny"
missing_docs = "deny"
Expand Down
4 changes: 3 additions & 1 deletion catalyst-gateway/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,6 @@ dotenvy = { workspace = true }
panic-message = { workspace = true }
cpu-time = { workspace = true }
ulid = { workspace = true, features = ["serde", "uuid"] }
rust-embed = { workspace = true }
rust-embed = { workspace = true }
local-ip-address.workspace = true
gethostname.workspace = true
4 changes: 2 additions & 2 deletions catalyst-gateway/bin/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ impl Cli {
logger::init(settings.log_level)?;

let state = Arc::new(State::new(Some(settings.database_url)).await?);
service::run(&settings.address, state).await?;
service::run(&settings.docs_settings, state).await?;
Ok(())
},
Self::Docs(settings) => {
let docs = service::get_app_docs();
let docs = service::get_app_docs(&settings);
match settings.output {
Some(path) => {
let mut docs_file = std::fs::File::create(path)?;
Expand Down
66 changes: 62 additions & 4 deletions catalyst-gateway/bin/src/service/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
//!
//! This defines all endpoints for the Catalyst Gateway API.
//! It however does NOT contain any processing for them, that is defined elsewhere.
use std::net::IpAddr;

use gethostname::gethostname;
use health::HealthApi;
use local_ip_address::list_afinet_netifas;
use poem_openapi::{ContactObject, LicenseObject, OpenApiService, ServerObject};
use registration::RegistrationApi;
use test_endpoints::TestApi;
use v0::V0Api;
use v1::V1Api;

use crate::settings::API_URL_PREFIX;
use crate::settings::{DocsSettings, API_URL_PREFIX};

mod health;
mod registration;
Expand Down Expand Up @@ -57,7 +61,7 @@ const TERMS_OF_SERVICE: &str =

/// Create the `OpenAPI` definition
pub(crate) fn mk_api(
hosts: Vec<String>,
hosts: Vec<String>, settings: &DocsSettings,
) -> OpenApiService<(TestApi, HealthApi, RegistrationApi, V0Api, V1Api), ()> {
let mut service = OpenApiService::new(
(TestApi, HealthApi, RegistrationApi, V0Api, V1Api),
Expand All @@ -70,10 +74,64 @@ pub(crate) fn mk_api(
.terms_of_service(TERMS_OF_SERVICE)
.url_prefix(API_URL_PREFIX.as_str());

// Add all the hosts where this API should be reachable.
// Retrieve the port from the socket address
let port = settings.address.port().to_string();

let server_name = &settings.server_name;

for host in hosts {
service = service.server(ServerObject::new(host));
service = service.server(ServerObject::new(host).description("API Host"));
}

// Add server name if it is set
if let Some(name) = server_name {
service = service.server(ServerObject::new(name).description("Server at server name"));
}

// Get localhost name
if let Ok(hostname) = gethostname().into_string() {
let hostname_addresses = add_protocol_prefix(
&format!("{hostname}:{port}"),
settings.http_auto_servers,
settings.https_auto_servers,
);
for hostname_address in hostname_addresses {
service = service.server(
ServerObject::new(hostname_address).description("Server at localhost name"),
);
}
}

// Get local IP address v4 and v6
if let Ok(network_interfaces) = list_afinet_netifas() {
for (name, ip) in &network_interfaces {
if *name == "en0" {
let (ip_with_port, desc) = match ip {
IpAddr::V4(_) => (format!("{ip}:{port}"), "Server at local IPv4 address"),
IpAddr::V6(_) => (format!("[{ip}]:{port}"), "Server at local IPv6 address"),
};
let ip_addresses = add_protocol_prefix(
&ip_with_port,
settings.http_auto_servers,
settings.https_auto_servers,
);
for address in ip_addresses {
service = service.server(ServerObject::new(address).description(desc));
}
}
}
}
service
}

/// Function to add protocol prefix based on flags.
fn add_protocol_prefix(address: &String, is_http: bool, is_https: bool) -> Vec<String> {
let mut addresses = Vec::new();
if is_http {
addresses.push(format!("http://{address}"));
}
if is_https {
addresses.push(format!("https://{address}"));
}
addresses
}
10 changes: 5 additions & 5 deletions catalyst-gateway/bin/src/service/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Main entrypoint to the service
use std::{net::SocketAddr, sync::Arc};
use std::sync::Arc;

use crate::state::State;
use crate::{settings::DocsSettings, state::State};

// These Modules contain endpoints
mod api;
Expand Down Expand Up @@ -34,14 +34,14 @@ pub(crate) enum Error {
///
/// ## Arguments
///
/// `service_addr`: &`SocketAddr` - the address to listen on
/// `settings`: &`DocsSetting` - settings for docs
/// `state`: `Arc<State>` - the state
///
/// ## Errors
///
/// `Error::CannotRunService` - cannot run the service
/// `Error::EventDbError` - cannot connect to the event db
/// `Error::IoError` - An IO error has occurred.
pub(crate) async fn run(service_addr: &SocketAddr, state: Arc<State>) -> Result<(), Error> {
poem_service::run(service_addr, state).await
pub(crate) async fn run(settings: &DocsSettings, state: Arc<State>) -> Result<(), Error> {
poem_service::run(settings, state).await
}
24 changes: 14 additions & 10 deletions catalyst-gateway/bin/src/service/poem_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//!
//! This provides only the primary entrypoint to the service.

use std::{net::SocketAddr, sync::Arc};
use std::sync::Arc;

use poem::{
endpoint::PrometheusExporter,
Expand All @@ -22,19 +22,21 @@ use crate::{
},
Error,
},
settings::{get_api_host_names, API_URL_PREFIX},
settings::{get_api_host_names, DocsSettings, API_URL_PREFIX},
state::State,
};

/// This exists to allow us to add extra routes to the service for testing purposes.
fn mk_app(hosts: Vec<String>, base_route: Option<Route>, state: &Arc<State>) -> impl Endpoint {
fn mk_app(
hosts: Vec<String>, base_route: Option<Route>, state: &Arc<State>, settings: &DocsSettings,
) -> impl Endpoint {
// Get the base route if defined, or a new route if not.
let base_route = match base_route {
Some(route) => route,
None => Route::new(),
};

let api_service = mk_api(hosts);
let api_service = mk_api(hosts, settings);
let docs = docs(&api_service);

let prometheus_registry = init_prometheus();
Expand All @@ -52,8 +54,8 @@ fn mk_app(hosts: Vec<String>, base_route: Option<Route>, state: &Arc<State>) ->
}

/// Get the API docs as a string in the JSON format.
pub(crate) fn get_app_docs() -> String {
let api_service = mk_api(vec![]);
pub(crate) fn get_app_docs(setting: &DocsSettings) -> String {
let api_service = mk_api(vec![], setting);
api_service.spec()
}

Expand All @@ -63,14 +65,16 @@ pub(crate) fn get_app_docs() -> String {
///
/// # Arguments
///
/// *`addr`: &`SocketAddr` - the address to listen on
/// *`settings`: &`DocsSetting` - settings for docs
///
/// # Errors
///
/// * `Error::CannotRunService` - cannot run the service
/// * `Error::EventDbError` - cannot connect to the event db
/// * `Error::IoError` - An IO error has occurred.
pub(crate) async fn run(addr: &SocketAddr, state: Arc<State>) -> Result<(), Error> {
pub(crate) async fn run(settings: &DocsSettings, state: Arc<State>) -> Result<(), Error> {
// The address to listen on
let addr = settings.address;
tracing::info!("Starting Poem Service ...");
tracing::info!("Listening on {addr}");

Expand All @@ -80,9 +84,9 @@ pub(crate) async fn run(addr: &SocketAddr, state: Arc<State>) -> Result<(), Erro
// help find them in the logs if they happen in production.
set_panic_hook();

let hosts = get_api_host_names(addr);
let hosts = get_api_host_names(&addr);

let app = mk_app(hosts, None, &state);
let app = mk_app(hosts, None, &state, settings);

poem::Server::new(TcpListener::bind(addr))
.run(app)
Expand Down
24 changes: 20 additions & 4 deletions catalyst-gateway/bin/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,40 @@ const API_URL_PREFIX_DEFAULT: &str = "/api";
/// and the logging level.
#[derive(Args, Clone)]
pub(crate) struct ServiceSettings {
/// Server binding address
#[clap(long, default_value = ADDRESS_DEFAULT)]
pub(crate) address: SocketAddr,

/// Url to the postgres event db
#[clap(long, env)]
pub(crate) database_url: String,

/// Logging level
#[clap(long, default_value = LOG_LEVEL_DEFAULT)]
pub(crate) log_level: LogLevel,

/// Docs settings.
#[clap(flatten)]
pub(crate) docs_settings: DocsSettings,
}

/// Settings specifies `OpenAPI` docs generation.
#[derive(Args, Clone)]
pub(crate) struct DocsSettings {
/// The output path to the generated docs file, if omitted prints to stdout.
pub(crate) output: Option<PathBuf>,

/// Server binding address
#[clap(long, default_value = ADDRESS_DEFAULT, env = "ADDRESS")]
pub(crate) address: SocketAddr,
bkioshn marked this conversation as resolved.
Show resolved Hide resolved

/// Flag for adding "http" to servers
#[clap(long, default_value = "false", env = "HTTP_AUTO_SERVERS")]
pub(crate) http_auto_servers: bool,
bkioshn marked this conversation as resolved.
Show resolved Hide resolved

/// Flag for adding "https" to servers
#[clap(long, default_value = "true", env = "HTTPS_AUTO_SERVERS")]
pub(crate) https_auto_servers: bool,
bkioshn marked this conversation as resolved.
Show resolved Hide resolved

/// Server name
#[clap(long, env = "SERVER_NAME")]
pub(crate) server_name: Option<String>,
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
}

/// An environment variable read as a string.
Expand Down
Loading