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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192

use serde::Deserialize;
use serde::Serialize;
use mime::Mime;

/// Defines how the response should be rendered.
pub enum HumusFormatFamily {

	/// When rendering the templating engine will be invoked.
	Template,
	
	/// When rendering the [View] is asked to [generate an API response].
	/// 
	/// [View]: ./trait.HumusView.html
	/// [generate an API response]: ./trait.HumusView.html#method.get_api_response
	API,
}

/// Provides information on what to render and how to deliver it to the [HumusEngine].
///
/// It is an integral part of a [HumusQuerySettings] implementation.
/// 
/// It is best implemented on an enum.
///
/// Also have a look at the provided methods! They may be sane defaults
/// for testing and prototyping, implementing them yourself on enum match logic
/// will improve performance and help with providing missing values.
///
/// For a quick prototype implementing ToString and the `from_name()`
/// method is enough in most cases.
///
/// Recommended macros:
/// ```
/// #[derive(Clone,Serialize,Deserialize,Default)]
/// #[serde(rename_all="lowercase")]
/// ```
///
/// For an example implementation see:
///  [HtmlTextJsonFormat](./enum.HtmlTextJsonFormat.html)
///
/// [HumusEngine]: ./struct.HumusEngine.html
/// [HumusQuerySettings]: ./trait.HumusQuerySettings.html
pub trait HumusFormat: ToString+Clone+Default+Send {

	/// Return wheter this format should be processed in Template or API mode.
	fn get_family(&self) -> HumusFormatFamily {
		match self.get_name().as_str() {
			"json" => HumusFormatFamily::API,
			_ => HumusFormatFamily::Template,
		}
	}

	/// Returns the file extnsion for the format.
	///
	/// Used for deriving the path for the template name.
	///
	/// Defaults to `.{self.get_name()}`
	/// with the exception of the name being `text`
	/// then it defaults to `.txt`.
	fn get_file_extension(&self) -> String {
		match self.get_name().as_str() {
			"text" => ".txt".to_string(),
			_ => ".".to_owned()+&self.get_name(),
		}
	}

	/// Returns the name of the format,
	/// by default taken from the ToString implementation.
	fn get_name(&self) -> String {
		self.to_string()
	}

	/// Allows adding extra mimetypes quickly for prototyping
	///
	/// Implementing get_mime_type() properly is recommended
	/// for production use. 
	fn get_less_well_known_mimetype(&self) -> Option<Mime> {
		None
	}

	/// Returns a textual representation of the Mimetype.
	///
	/// It is recommended to implement this when in production use.
	///
	/// For prototyping the default implementation makes assumptions
	/// based on the output of get_name(), falling back
	/// to get_less_well_known_mimetype() and the "application/octet-stream" type.
	///
	/// The default implementation knows the following associations:
	/// * `text`: `text/plain; charset=utf-8`
	/// * `html`: `text/html; charset=utf-8`
	/// * `json`: `application/json`
	/// * `xml`: `application/xml`
	/// * `rss`: `application/rss+xml`
	/// * `atom`: `application/atom+xml`
	///
	/// *Implementation Note:* It may be possible that two different views
	/// have the same MimeType (maybe two json representations for different consumers).
	/// 
	fn get_mime_type(&self) -> Mime {
		match self.get_name().as_str() {
			"text" => mime::TEXT_PLAIN_UTF_8,
			"html" => mime::TEXT_HTML_UTF_8,
			"json" => mime::APPLICATION_JSON,
			"xml" => "application/xml".parse()
				.expect("Parse static application/xml"),
			"rss" => "application/rss+xml".parse()
				.expect("Parse static application/rss+xml"),
			"atom" =>  "application/atom+xml".parse()
				.expect("Parse static application/atom+xml"),
			_ =>
				self.get_less_well_known_mimetype()
					.unwrap_or(mime::APPLICATION_OCTET_STREAM),
		}
	}

	/// Constructs a view from its name.
	fn from_name(name: &str) -> Option<Self>;
}

// Some Sample implementations

/// Ready to use example implementation of a [HumusFormat]
/// featuring `html`, `text` and `json`.
///
/// When using this it is recommended to crete a type alias
/// in case this isn't sufficient in the future.
/// ```
/// type MyResponseFormat = lib_humus::HtmlTextJsonFormat;
/// ```
#[derive(Clone,Serialize,Deserialize,Default)]
#[serde(rename_all="lowercase")]
pub enum HtmlTextJsonFormat {

	/// An html response.
	#[default]
	Html,

	/// A plain text response to be viewed in a terminal.
	Text,

	/// A json response for other programs.
	Json,
}

impl ToString for HtmlTextJsonFormat {
	fn to_string(&self) -> String {
		match self {
			Self::Html => "html",
			Self::Text => "text",
			Self::Json => "json",
		}.to_owned()
	}
}


impl HumusFormat for HtmlTextJsonFormat {

	fn get_family(&self) -> HumusFormatFamily {
		match self {
			Self::Json => HumusFormatFamily::API,
			_ => HumusFormatFamily::Template,
		}
	}

	fn get_file_extension(&self) -> String {
		match self {
			Self::Text => ".txt",
			Self::Html => ".html",
			Self::Json => ".json",
		}.to_string()
	}

	fn get_mime_type(&self) -> Mime {
		match self {
			Self::Text => mime::TEXT_PLAIN_UTF_8,
			Self::Html => mime::TEXT_HTML_UTF_8,
			Self::Json => mime::APPLICATION_JSON,
		}
	}

	fn from_name(name: &str) -> Option<Self> {
		match name {
			"html" => Some(Self::Html),
			"text" => Some(Self::Text),
			"json" => Some(Self::Json),
			_ => None,
		}
	}
}