cat_gateway/service/api/
mod.rs

1//! Catalyst Gateway API Definition
2//!
3//! This defines all endpoints for the Catalyst Gateway API.
4//! It however does NOT contain any processing for them, that is defined elsewhere.
5use std::net::IpAddr;
6
7use config::ConfigApi;
8use documents::DocumentApi;
9use gethostname::gethostname;
10use health::HealthApi;
11use local_ip_address::list_afinet_netifas;
12use poem_openapi::{ContactObject, LicenseObject, OpenApiService, ServerObject};
13
14use self::cardano::CardanoApi;
15use crate::settings::Settings;
16
17pub(crate) mod cardano;
18mod config;
19mod documents;
20pub(crate) mod health;
21
22/// The name of the API
23const API_TITLE: &str = "Catalyst Gateway";
24
25/// The version of the API
26const API_VERSION: &str = "0.8.0";
27
28/// Get the contact details for inquiring about the API
29fn get_api_contact() -> ContactObject {
30    ContactObject::new()
31        .name("Project Catalyst Team")
32        .email("contact@projectcatalyst.io")
33        .url("https://projectcatalyst.io")
34}
35
36/// A long description of the API. Markdown is supported
37const API_DESCRIPTION: &str = "# Catalyst Gateway API.
38
39The Catalyst Gateway API provides realtime data for all prior, current and future Catalyst Voices voting events.
40
41⚠️ Warning: This API is currently unstable and may change at any time without prior notice. Backwards compatibility is not guaranteed, and future versions may introduce breaking changes. It is intended for experimental or internal use only. Use at your own risk.
42";
43
44/// Get the license details for the API
45fn get_api_license() -> LicenseObject {
46    LicenseObject::new("Apache 2.0").url("https://www.apache.org/licenses/LICENSE-2.0")
47}
48
49/// Get the terms of service for the API
50const TERMS_OF_SERVICE: &str =
51    "https://github.com/input-output-hk/catalyst-voices/blob/main/CODE_OF_CONDUCT.md";
52
53/// Create the `OpenAPI` definition
54pub(crate) fn mk_api() -> OpenApiService<(HealthApi, CardanoApi, ConfigApi, DocumentApi), ()> {
55    let mut service = OpenApiService::new(
56        (
57            HealthApi,
58            (
59                cardano::rbac::Api,
60                cardano::staking::Api,
61                cardano::cip36::Api,
62            ),
63            ConfigApi,
64            DocumentApi,
65        ),
66        API_TITLE,
67        API_VERSION,
68    )
69    .contact(get_api_contact())
70    .description(API_DESCRIPTION)
71    .license(get_api_license())
72    .terms_of_service(TERMS_OF_SERVICE)
73    .url_prefix(Settings::api_url_prefix());
74
75    let hosts = Settings::api_host_names();
76
77    if hosts.is_empty() {
78        service = set_localhost_addresses(service);
79    } else {
80        for host in hosts {
81            service = service.server(
82                ServerObject::new(host).description("Server host staging/production location."),
83            );
84        }
85    }
86
87    // Add server name if it is set
88    if let Some(name) = Settings::server_name() {
89        service = service.server(ServerObject::new(name).description("Server at server name."));
90    }
91    service
92}
93
94/// Set the localhost addresses descriptions.
95fn set_localhost_addresses<T>(mut service: OpenApiService<T, ()>) -> OpenApiService<T, ()> {
96    let port = Settings::bound_address().port();
97
98    // Get localhost name
99    if let Ok(hostname) = gethostname().into_string() {
100        let hostname_address = format!("http://{hostname}:{port}",);
101        service = service
102            .server(ServerObject::new(hostname_address).description("Server at localhost name."));
103    }
104
105    // Get local IP address v4 and v6
106    if let Ok(network_interfaces) = list_afinet_netifas() {
107        for (name, ip) in &network_interfaces {
108            if *name == "en0" {
109                let (address, desc) = match ip {
110                    IpAddr::V4(_) => {
111                        (
112                            format!("http://{ip}:{port}"),
113                            "Server at local IPv4 address.",
114                        )
115                    },
116                    IpAddr::V6(_) => {
117                        (
118                            format!("http://[{ip}]:{port}"),
119                            "Server at local IPv6 address.",
120                        )
121                    },
122                };
123                service = service.server(ServerObject::new(address).description(desc));
124            }
125        }
126    }
127    service
128}