pib-viewer 0.8.0

A viewer for public governmental data served over OParl
// 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::{fmt::Display, str::FromStr};

use dioxus::prelude::{VNode, rsx};
use dioxus_router::Routable;
use oparl_types::{AgendaItemUrl, BodyUrl, MeetingUrl, OrganizationUrl, PersonUrl, SystemUrl};

use crate::views::{AgendaItem, Body, Home, Meeting, OParlSystem, Organization, Person};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UrlEncoded<T: Display + FromStr> {
    Valid(T),
    Invalid(String),
}

impl<T: Display + FromStr> From<T> for UrlEncoded<T> {
    fn from(value: T) -> Self {
        Self::Valid(value)
    }
}

impl<T: Display + FromStr> UrlEncoded<T> {
    pub fn get_inner(&self) -> Option<&T> {
        let Self::Valid(inner) = self else {
            return None;
        };
        Some(inner)
    }
}

impl<T: Display + FromStr> Display for UrlEncoded<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            UrlEncoded::Valid(value) => write!(f, "{}", urlencoding::encode(&value.to_string())),
            UrlEncoded::Invalid(invalid) => write!(f, "{invalid}"),
        }
    }
}

impl<T: Display + FromStr> FromStr for UrlEncoded<T> {
    type Err = T::Err;

    fn from_str(s: &str) -> Result<UrlEncoded<T>, Self::Err> {
        match urlencoding::decode(s) {
            Ok(decoded) => {
                if let Ok(parsed) = decoded.parse() {
                    Ok(Self::Valid(parsed))
                } else {
                    Ok(Self::Invalid(s.to_string()))
                }
            }
            Err(_e) => Ok(Self::Invalid(s.to_string())),
        }
    }
}

#[derive(Debug, Clone, Routable, PartialEq)]
pub(crate) enum Route {
    #[route("/")]
    Home {},

    #[route("/system/:system_url")]
    OParlSystem { system_url: UrlEncoded<SystemUrl> },

    #[route("/body/:body_url")]
    Body { body_url: UrlEncoded<BodyUrl> },

    #[route("/organization/:organization_url")]
    Organization {
        organization_url: UrlEncoded<OrganizationUrl>,
    },

    #[route("/person/:person_url")]
    Person { person_url: UrlEncoded<PersonUrl> },

    #[route("/meeting/:meeting_url")]
    Meeting { meeting_url: UrlEncoded<MeetingUrl> },

    #[route("/agenda_item/:agenda_item_url")]
    AgendaItem {
        agenda_item_url: UrlEncoded<AgendaItemUrl>,
    },
}