pib-service 0.17.0

Politik im Blick service for serving public governmental information in OParl format, with some extra addon information
// SPDX-FileCopyrightText: Politik im Blick developers
// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>
//
// SPDX-License-Identifier: AGPL-3.0-or-later OR EUPL-1.2

use std::{net::SocketAddr, path::Path};

use anyhow::bail;
use config::FileFormat;
use dirs::config_dir;
use serde::{Deserialize, Serialize};

use crate::PROGRAM_NAME;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Settings {
    pub oparl: OParlSettings,
    pub http: HttpSettings,
    pub api: Option<ApiSettings>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OParlSettings {
    pub base_url: oparl_types::Url,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HttpSettings {
    pub bind: Vec<SocketAddr>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiSettings {
    pub oidc: ApiOidcSettings,
    pub http: HttpSettings,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiOidcSettings {
    pub issuer: String,
    pub client_id: String,
    pub client_secret: String,
}

impl Settings {
    pub fn load() -> anyhow::Result<Self> {
        let file_name = format!("{PROGRAM_NAME}.toml");
        let config_paths = vec![
            config_dir().unwrap().join(PROGRAM_NAME).join(&file_name),
            Path::new("/etc").join(PROGRAM_NAME).join(&file_name),
        ];

        for config_path in &config_paths {
            if config_path.exists() {
                return Self::load_from_file(config_path);
            }
        }

        bail!(
            "Couldn't find a valid configuration file, searched in these paths: {config_paths:?}"
        );
    }

    fn load_from_file(path: &Path) -> anyhow::Result<Self> {
        log::info!("Loading configuration file {path:?}");
        let config = config::Config::builder()
            .add_source(
                config::File::from(path)
                    .required(true)
                    .format(FileFormat::Toml),
            )
            .build()?;
        Ok(config.try_deserialize::<Settings>()?)
    }
}