1use std::borrow::Cow;
23
24pub const DEFAULT_CONTENT_TYPE: &str = "application/octet-stream";
26
27pub trait ContentTypeResolver: Send + Sync {
29 fn resolve(&self, data: &[u8]) -> Cow<'static, str>;
32}
33
34impl<F> ContentTypeResolver for F
35where
36 F: Fn(&[u8]) -> Cow<'static, str> + Send + Sync,
37{
38 fn resolve(&self, data: &[u8]) -> Cow<'static, str> {
39 (self)(data)
40 }
41}
42
43#[cfg(feature = "file-format")]
49pub fn default_resolver(data: &[u8]) -> Cow<'static, str> {
50 #[cfg(feature = "serde_json")]
51 if serde_json::from_slice::<serde_json::Value>(data).is_ok() {
52 match serde_json::from_slice(data).unwrap() {
54 serde_json::Value::String(_)
55 | serde_json::Value::Bool(_)
56 | serde_json::Value::Number(_)
57 | serde_json::Value::Null => return Cow::Borrowed("text/plain"),
58
59 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
60 return Cow::Borrowed("application/json; charset=utf-8")
61 }
62 }
63 }
64
65 #[cfg(feature = "serde_yaml_ng")]
66 if serde_yaml_ng::from_slice::<serde_yaml_ng::Value>(data).is_ok() {
67 fn match_value(value: &serde_yaml_ng::Value) -> Cow<'static, str> {
68 match value {
69 serde_yaml_ng::Value::Bool(_)
70 | serde_yaml_ng::Value::Number(_)
71 | serde_yaml_ng::Value::String(_)
72 | serde_yaml_ng::Value::Null => Cow::Borrowed("text/plain"),
73
74 serde_yaml_ng::Value::Tagged(m) => match_value(&m.value),
75 serde_yaml_ng::Value::Mapping(_) | serde_yaml_ng::Value::Sequence(_) => {
76 Cow::Borrowed("text/yaml; charset=utf-8")
77 }
78 }
79 }
80
81 return match_value(&serde_yaml_ng::from_slice(data).unwrap());
82 }
83
84 infer::get(data).map(|ty| Cow::Borrowed(ty.mime_type())).unwrap_or({
85 let format = file_format::FileFormat::from_bytes(data);
86 Cow::Owned(format.media_type().to_owned())
87 })
88}
89
90#[cfg(not(feature = "file-format"))]
93pub fn default_resolver(data: &[u8]) -> Cow<'static, str> {
94 #[cfg(feature = "serde_json")]
95 if serde_json::from_slice::<serde_json::Value>(data).is_ok() {
96 match serde_json::from_slice(data).unwrap() {
98 serde_json::Value::String(_)
99 | serde_json::Value::Bool(_)
100 | serde_json::Value::Number(_)
101 | serde_json::Value::Null => return Cow::Borrowed("text/plain"),
102
103 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
104 return Cow::Borrowed("application/json; charset=utf-8")
105 }
106 }
107 }
108
109 #[cfg(feature = "serde_yaml_ng")]
110 if serde_yaml_ng::from_slice::<serde_yaml_ng::Value>(data).is_ok() {
111 fn match_value(value: &serde_yaml_ng::Value) -> Cow<'static, str> {
112 match value {
113 serde_yaml_ng::Value::Bool(_)
114 | serde_yaml_ng::Value::Number(_)
115 | serde_yaml_ng::Value::String(_)
116 | serde_yaml_ng::Value::Null => Cow::Borrowed("text/plain"),
117
118 serde_yaml_ng::Value::Tagged(m) => match_value(&m.value),
119 serde_yaml_ng::Value::Mapping(_) | serde_yaml_ng::Value::Sequence(_) => {
120 Cow::Borrowed("text/yaml; charset=utf-8")
121 }
122 }
123 }
124
125 return match_value(&serde_yaml_ng::from_slice(data).unwrap());
126 }
127
128 DEFAULT_CONTENT_TYPE.into()
129}
130
131#[cfg(test)]
132#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
133mod tests {
134 use super::default_resolver;
135
136 #[cfg(feature = "file-format")]
137 #[test]
138 fn test_other_stuff() {
139 assert_eq!("text/plain", default_resolver(b"some plain text"));
140 assert_eq!("image/jpeg", default_resolver(&[0xFF, 0xD8, 0xFF, 0xAA]));
141 }
142
143 #[cfg(feature = "serde_json")]
144 #[test]
145 fn test_json() {
146 use serde_json::{json, to_vec};
147
148 for (value, assertion) in [
149 (json!(null), "text/plain"),
150 (json!(true), "text/plain"),
151 (json!(false), "text/plain"),
152 (json!("any string"), "text/plain"),
153 (json!(1.2), "text/plain"),
154 (json!({ "hello": "world" }), "application/json; charset=utf-8"),
155 (json!(["hello", "world"]), "application/json; charset=utf-8"),
156 ] {
157 assert_eq!(
158 assertion,
159 default_resolver(&to_vec(&value).expect("failed to convert to JSON"))
160 );
161 }
162 }
163
164 #[cfg(feature = "serde_yaml_ng")]
165 #[test]
166 fn test_yaml() {
167 for (value, assertion) in [
168 (serde_yaml_ng::Value::Null, "text/plain"),
169 (serde_yaml_ng::Value::Bool(true), "text/plain"),
170 (serde_yaml_ng::Value::Bool(false), "text/plain"),
171 (serde_yaml_ng::Value::String("hello world".into()), "text/plain"),
172 (serde_yaml_ng::Value::Number(1.into()), "text/plain"),
173 (
174 serde_yaml_ng::Value::Sequence(vec![serde_yaml_ng::Value::Bool(true)]),
175 "text/yaml; charset=utf-8",
176 ),
177 (
178 serde_yaml_ng::Value::Mapping({
179 let mut map = serde_yaml_ng::Mapping::new();
180 map.insert(
181 serde_yaml_ng::Value::String("hello".into()),
182 serde_yaml_ng::Value::String("world".into()),
183 );
184
185 map
186 }),
187 "text/yaml; charset=utf-8",
188 ),
189 ] {
190 assert_eq!(
191 assertion,
192 default_resolver(
193 serde_yaml_ng::to_string(&value)
194 .expect("failed to parse YAML")
195 .as_bytes()
196 )
197 );
198 }
199 }
200}