1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
pub type Result = std::result::Result<ft_sdk::chr::CHR<Output>, ft_sdk::Error>;

#[derive(Debug)]
pub enum Output {
    Json(serde_json::Value),
    Binary(Binary),
}

#[derive(Debug)]
pub struct Binary {
    pub file_name: Option<String>,
    pub content: bytes::Bytes,
    pub content_type: String,
}

pub(crate) fn binary_response(
    binary: Binary,
) -> std::result::Result<::http::Response<bytes::Bytes>, ft_sdk::Error> {
    let mut response_builder = ::http::Response::builder()
        .status(200)
        .header("Content-Type", binary.content_type.as_str());

    // Add the binary as attachment, indicating it should be downloaded
    if let Some(filename) = binary.file_name {
        response_builder = response_builder.header(
            "Content-Disposition",
            format!("attachment; filename=\"{filename}\"").as_str(),
        )
    }

    Ok(response_builder.body(binary.content)?)
}

impl From<ft_sdk::chr::CHR<Output>>
    for std::result::Result<http::Response<bytes::Bytes>, ft_sdk::Error>
{
    fn from(
        ft_sdk::chr::CHR {
            cookies,
            headers,
            response,
        }: ft_sdk::chr::CHR<Output>,
    ) -> Self {
        let response = match response {
            Output::Json(json_value) => ft_sdk::json(json_value),
            Output::Binary(binary) => binary_response(binary),
        }?;
        ft_sdk::chr::chr(cookies, headers, response)
    }
}

/// Creates a response that instructs browser to store in downloads the binary content provided.
///
/// # Parameters
/// - `filename`: An optional `String` representing the name of the file. If provided, the response
///   will add `Content-Disposition: attachment; filename="{filename}"` as header, indicating the
///   binary should be downloaded and this name will be used as the filename for the download.
/// - `content`: A `bytes::Bytes` object containing the binary content.
/// - `content_type`: An `AsRef<str>` (&str/String) specifying the MIME type of the content
///    (e.g., `application/pdf`, `image/png`).
///
/// # Example
///
/// ```rust
/// let content = bytes::Bytes::from("This is the content of the file.");
///
/// ft_sdk::data::download("example.txt", content, "text/plain").unwrap();
/// ```
pub fn download<S1: AsRef<str>, S2: AsRef<str>>(
    file_name: S1,
    content: bytes::Bytes,
    content_type: S2,
) -> Result {
    Ok(ft_sdk::chr::CHR::new(Output::Binary(Binary {
        file_name: Some(file_name.as_ref().to_string()),
        content,
        content_type: content_type.as_ref().to_string(),
    })))
}

/// Creates a binary response for serving binary data over HTTP.
///
/// # Parameters
/// - `content`: A `bytes::Bytes` object containing the binary content.
/// - `content_type`: An `AsRef<str>` (&str/String) specifying the MIME type of the content
///    (e.g., `application/pdf`, `image/png`).
///
/// # Example
///
/// ```rust
/// let content = bytes::Bytes::from("This is the content of the file.");
///
/// ft_sdk::data::binary(content, "text/plain").unwrap();
/// ```
pub fn binary<S: AsRef<str>>(content: bytes::Bytes, content_type: S) -> Result {
    Ok(ft_sdk::chr::CHR::new(Output::Binary(Binary {
        file_name: None,
        content,
        content_type: content_type.as_ref().to_string(),
    })))
}

pub fn json<T: serde::Serialize>(t: T) -> Result {
    Ok(ft_sdk::chr::CHR::new(Output::Json(serde_json::to_value(
        t,
    )?)))
}

pub fn api_ok<T: serde::Serialize>(t: T) -> Result {
    Ok(ft_sdk::chr::CHR::new(Output::Json(
        serde_json::json!({"data": serde_json::to_value(t)?, "success": true }),
    )))
}

pub fn api_error(errors: std::collections::HashMap<String, String>) -> Result {
    Ok(ft_sdk::chr::CHR::new(Output::Json(
        serde_json::json!({"errors": serde_json::to_value(errors)?, "success": false }),
    )))
}