df_st_api 0.3.0-development-2

Starting an API server for the DF Storyteller project.
use crate::api_objects::ApiObject;
use crate::pagination::{ApiItemQuery, ApiPage};
use crate::ServerInfo;
use df_st_core::SchemaExample;
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use rocket::State;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;

/// A Wrapper for an `ApiObject` that includes additional metadata.
#[derive(Serialize, Deserialize, Clone, Debug, Default, JsonSchema)]
#[schemars(example = "Self::example")]
pub struct ApiItem<D>
where
    D: ApiObject + Serialize + SchemaExample,
{
    /// A unique string identifier for this type.
    #[serde(rename = "_type")]
    pub type_: String,
    /// Links the various related items.
    #[serde(rename = "_links")]
    pub links: ApiItemLinks,
    /// Don't load nested items. This will speed up page loads but will not fill in all the data.
    /// Fields that can be used for ordering are usually the only field that are returned.
    /// - `Some(false)` If value is not set, or set to "false" or "0" (same result default)
    /// - `Some(true)` If value is set to ""(empty), "true" or "1"
    /// - `None` If any other value this will add all the data to the response (default)
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename = "_minimal_data")]
    pub minimal_data: Option<bool>,
    /// The included data item.
    #[serde(flatten)]
    pub data: D,
    /// Server base url.
    /// Not included in response.
    #[serde(skip)]
    pub base_url: String,
}

impl<D> SchemaExample for ApiItem<D>
where
    D: SchemaExample + Serialize + ApiObject,
{
    fn example() -> Self {
        Self {
            type_: D::get_type(),
            links: ApiItemLinks {
                self_: Some(D::example().get_item_link(&"http://127.0.0.1:20350/api".to_owned())),
            },
            minimal_data: Some(false),
            data: D::example(),
            base_url: "".to_owned(),
        }
    }
}

impl<D> ApiItem<D>
where
    D: ApiObject + Default + Serialize + SchemaExample,
{
    pub fn new(server_info: &State<ServerInfo>, item_query: &ApiItemQuery) -> Self {
        let server_info = server_info.inner().clone();
        Self {
            minimal_data: item_query.minimal_data,
            base_url: server_info.base_url,
            ..Default::default()
        }
    }

    fn set_links(&mut self) {
        let mut query_parameters = Vec::new();

        if let Some(minimal_data) = &self.minimal_data {
            query_parameters.push(format!("minimal_data={}", minimal_data));
        }

        // Self
        let mut links = ApiItemLinks::default();
        let base_api_path = self.data.get_item_link(&self.base_url);
        if query_parameters.is_empty() {
            links.self_ = Some(base_api_path);
        } else {
            links.self_ = Some(format!("{}?{}", base_api_path, query_parameters.join("&")));
        }

        self.links = links;
    }

    /// Store the data in the object and construct links.
    pub fn wrap(&mut self, item: D) {
        self.data = item;
        self.type_ = D::get_type();
        self.set_links();
    }

    /// Create and wrap the item.
    pub fn wrap_new(item: D, parent: &ApiPage<D>) -> ApiItem<D> {
        let mut return_object = ApiItem {
            base_url: parent.base_url.to_string(),
            minimal_data: parent.minimal_data,
            ..Default::default()
        };
        return_object.wrap(item);
        return_object
    }

    pub fn get_nested_items(&self) -> bool {
        match self.minimal_data {
            Some(true) => false,
            Some(false) => true,
            // default is to add data
            None => true,
        }
    }
}

/// Links related to a specific `ApiObject`.
#[derive(Serialize, Deserialize, Clone, Debug, Default, JsonSchema)]
#[schemars(example = "Self::example")]
pub struct ApiItemLinks {
    /// The url to request this individual item.
    #[serde(rename = "self")]
    pub self_: Option<String>,
}

impl SchemaExample for ApiItemLinks {
    fn example() -> Self {
        Self {
            self_: Some("http://127.0.0.1:20350/api/examples/5".to_owned()),
        }
    }
}