Skip to content

Commit 809b3db

Browse files
authored
feat(rust/signed-doc): Add more supporting media types (#475)
* feat: intial declaration * feat: intial declaration * feat: media types defining * feat: cbor decoding * test: initial * chore: lintfix * chore: lintfix * test: restructure using test cases * chore: lintfix
1 parent 77e1ddc commit 809b3db

File tree

4 files changed

+181
-74
lines changed

4 files changed

+181
-74
lines changed

rust/signed_doc/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ brotli = "7.0.0"
2222
ed25519-dalek = { version = "2.1.1", features = ["rand_core", "pem"] }
2323
hex = "0.4.3"
2424
strum = { version = "0.27.1", features = ["derive"] }
25+
strum_macros = { version = "0.27.1" }
2526
clap = { version = "4.5.23", features = ["derive", "env"] }
2627
jsonschema = "0.28.3"
2728
jsonpath-rust = "0.7.5"
Lines changed: 158 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,48 @@
11
//! Document Payload Content Type.
22
3-
use std::{
4-
fmt::{Display, Formatter},
5-
str::FromStr,
6-
};
3+
use std::{str::FromStr, string::ToString};
74

85
use strum::VariantArray;
96

107
/// Payload Content Type.
11-
#[derive(Debug, Copy, Clone, PartialEq, Eq, VariantArray)]
8+
#[derive(Debug, Copy, Clone, PartialEq, Eq, VariantArray, strum_macros::Display)]
129
pub enum ContentType {
1310
/// `application/cbor`
11+
#[strum(to_string = "application/cbor")]
1412
Cbor,
1513
/// `application/cddl`
14+
#[strum(to_string = "application/cddl")]
1615
Cddl,
1716
/// `application/json`
17+
#[strum(to_string = "application/json")]
1818
Json,
1919
/// `application/json+schema`
20+
#[strum(to_string = "application/json+schema")]
2021
JsonSchema,
21-
}
22-
23-
impl Display for ContentType {
24-
fn fmt(
25-
&self,
26-
f: &mut Formatter<'_>,
27-
) -> Result<(), std::fmt::Error> {
28-
match self {
29-
Self::Cbor => write!(f, "application/cbor"),
30-
Self::Cddl => write!(f, "application/cddl"),
31-
Self::Json => write!(f, "application/json"),
32-
Self::JsonSchema => write!(f, "application/json+schema"),
33-
}
34-
}
22+
/// `text/css; charset=utf-8`
23+
#[strum(to_string = "text/css; charset=utf-8")]
24+
Css,
25+
/// `text/css; charset=utf-8; template=handlebars`
26+
#[strum(to_string = "text/css; charset=utf-8; template=handlebars")]
27+
CssHandlebars,
28+
/// `text/html; charset=utf-8`
29+
#[strum(to_string = "text/html; charset=utf-8")]
30+
Html,
31+
/// `text/html; charset=utf-8; template=handlebars`
32+
#[strum(to_string = "text/html; charset=utf-8; template=handlebars")]
33+
HtmlHandlebars,
34+
/// `text/markdown; charset=utf-8`
35+
#[strum(to_string = "text/markdown; charset=utf-8")]
36+
Markdown,
37+
/// `text/markdown; charset=utf-8; template=handlebars`
38+
#[strum(to_string = "text/markdown; charset=utf-8; template=handlebars")]
39+
MarkdownHandlebars,
40+
/// `text/plain; charset=utf-8`
41+
#[strum(to_string = "text/plain; charset=utf-8")]
42+
Plain,
43+
/// `text/plain; charset=utf-8; template=handlebars`
44+
#[strum(to_string = "text/plain; charset=utf-8; template=handlebars")]
45+
PlainHandlebars,
3546
}
3647

3748
impl FromStr for ContentType {
@@ -43,6 +54,14 @@ impl FromStr for ContentType {
4354
"application/cddl" => Ok(Self::Cddl),
4455
"application/json" => Ok(Self::Json),
4556
"application/json+schema" => Ok(Self::JsonSchema),
57+
"text/css; charset=utf-8" => Ok(Self::Css),
58+
"text/css; charset=utf-8; template=handlebars" => Ok(Self::CssHandlebars),
59+
"text/html; charset=utf-8" => Ok(Self::Html),
60+
"text/html; charset=utf-8; template=handlebars" => Ok(Self::HtmlHandlebars),
61+
"text/markdown; charset=utf-8" => Ok(Self::Markdown),
62+
"text/markdown; charset=utf-8; template=handlebars" => Ok(Self::MarkdownHandlebars),
63+
"text/plain; charset=utf-8" => Ok(Self::Plain),
64+
"text/plain; charset=utf-8; template=handlebars" => Ok(Self::PlainHandlebars),
4665
_ => {
4766
anyhow::bail!(
4867
"Unsupported Content Type: {s:?}, Supported only: {:?}",
@@ -56,6 +75,25 @@ impl FromStr for ContentType {
5675
}
5776
}
5877

78+
impl TryFrom<u64> for ContentType {
79+
type Error = anyhow::Error;
80+
81+
fn try_from(value: u64) -> Result<Self, Self::Error> {
82+
// https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#content-formats
83+
match value {
84+
0 => Ok(Self::Plain),
85+
50 => Ok(Self::Json),
86+
60 => Ok(Self::Cbor),
87+
20000 => Ok(Self::Css),
88+
_ => {
89+
anyhow::bail!(
90+
"Unsupported CoAP Content-Format: {value}, Supported only: 0, 50, 60, 20000",
91+
)
92+
},
93+
}
94+
}
95+
}
96+
5997
impl<'de> serde::Deserialize<'de> for ContentType {
6098
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
6199
where D: serde::Deserializer<'de> {
@@ -94,61 +132,114 @@ impl minicbor::Decode<'_, ()> for ContentType {
94132
_ctx: &mut (),
95133
) -> Result<Self, minicbor::decode::Error> {
96134
let p = d.position();
97-
match d.int() {
98-
// CoAP Content Format JSON
99-
Ok(val) if val == minicbor::data::Int::from(50_u8) => Ok(Self::Json),
100-
// CoAP Content Format CBOR
101-
Ok(val) if val == minicbor::data::Int::from(60_u8) => Ok(Self::Cbor),
102-
Ok(val) => {
103-
Err(minicbor::decode::Error::message(format!(
104-
"unsupported CoAP Content Formats value: {val}"
105-
)))
106-
},
107-
Err(_) => {
108-
d.set_position(p);
109-
d.str()?.parse().map_err(minicbor::decode::Error::message)
110-
},
135+
if let Ok(val) = d.int() {
136+
let val: u64 = val.try_into().map_err(minicbor::decode::Error::custom)?;
137+
Self::try_from(val).map_err(minicbor::decode::Error::message)
138+
} else {
139+
d.set_position(p);
140+
d.str()?.parse().map_err(minicbor::decode::Error::message)
111141
}
112142
}
113143
}
114144

115145
#[cfg(test)]
116146
mod tests {
147+
use minicbor::{Decode, Decoder, Encoder};
148+
use test_case::test_case;
149+
117150
use super::*;
118151

119-
#[test]
120-
fn content_type_string_test() {
121-
assert_eq!(
122-
ContentType::from_str("application/cbor").unwrap(),
123-
ContentType::Cbor
124-
);
125-
assert_eq!(
126-
ContentType::from_str("application/cddl").unwrap(),
127-
ContentType::Cddl
128-
);
129-
assert_eq!(
130-
ContentType::from_str("application/json").unwrap(),
131-
ContentType::Json
132-
);
133-
assert_eq!(
134-
ContentType::from_str("application/json+schema").unwrap(),
135-
ContentType::JsonSchema
136-
);
137-
assert_eq!(
138-
"application/cbor".parse::<ContentType>().unwrap(),
139-
ContentType::Cbor
140-
);
141-
assert_eq!(
142-
"application/cddl".parse::<ContentType>().unwrap(),
143-
ContentType::Cddl
144-
);
145-
assert_eq!(
146-
"application/json".parse::<ContentType>().unwrap(),
147-
ContentType::Json
148-
);
149-
assert_eq!(
150-
"application/json+schema".parse::<ContentType>().unwrap(),
151-
ContentType::JsonSchema
152-
);
152+
#[test_case(
153+
("application/cbor", ContentType::Cbor);
154+
"application/cbor"
155+
)]
156+
#[test_case(
157+
("application/cddl", ContentType::Cddl);
158+
"application/cddl"
159+
)]
160+
#[test_case(
161+
("application/json", ContentType::Json);
162+
"application/json"
163+
)]
164+
#[test_case(
165+
("application/json+schema", ContentType::JsonSchema);
166+
"application/json+schema"
167+
)]
168+
#[test_case(
169+
("text/css; charset=utf-8", ContentType::Css);
170+
"text/css; charset=utf-8"
171+
)]
172+
#[test_case(
173+
("text/css; charset=utf-8; template=handlebars", ContentType::CssHandlebars);
174+
"text/css; charset=utf-8; template=handlebars"
175+
)]
176+
#[test_case(
177+
("text/html; charset=utf-8", ContentType::Html);
178+
"text/html; charset=utf-8"
179+
)]
180+
#[test_case(
181+
("text/html; charset=utf-8; template=handlebars", ContentType::HtmlHandlebars);
182+
"text/html; charset=utf-8; template=handlebars"
183+
)]
184+
#[test_case(
185+
("text/markdown; charset=utf-8", ContentType::Markdown);
186+
"text/markdown; charset=utf-8"
187+
)]
188+
#[test_case(
189+
("text/markdown; charset=utf-8; template=handlebars", ContentType::MarkdownHandlebars);
190+
"text/markdown; charset=utf-8; template=handlebars"
191+
)]
192+
#[test_case(
193+
("text/plain; charset=utf-8", ContentType::Plain);
194+
"text/plain; charset=utf-8"
195+
)]
196+
#[test_case(
197+
("text/plain; charset=utf-8; template=handlebars", ContentType::PlainHandlebars);
198+
"text/plain; charset=utf-8; template=handlebars"
199+
)]
200+
fn content_type_string_test((raw_str, variant): (&str, ContentType)) {
201+
// from str
202+
assert_eq!(ContentType::from_str(raw_str).unwrap(), variant);
203+
204+
// parsing
205+
assert_eq!(raw_str.parse::<ContentType>().unwrap(), variant);
206+
207+
// decoding from cbor
208+
let mut e = Encoder::new(vec![]);
209+
e.str(raw_str).unwrap();
210+
let bytes = e.into_writer().clone();
211+
let mut decoder = Decoder::new(bytes.as_slice());
212+
213+
assert_eq!(ContentType::decode(&mut decoder, &mut ()).unwrap(), variant);
214+
}
215+
216+
#[test_case(
217+
(&[0x00], ContentType::Plain);
218+
"text/plain; charset=utf-8"
219+
)]
220+
#[test_case(
221+
(&[0x18, 0x32], ContentType::Json);
222+
"application/json"
223+
)]
224+
#[test_case(
225+
(&[0x18, 0x3C], ContentType::Cbor);
226+
"application/cbor"
227+
)]
228+
#[test_case(
229+
(&[0x19, 0x4E, 0x20], ContentType::Css);
230+
"text/css; charset=utf-8"
231+
)]
232+
fn cbor_coap_decoding_test((coap_code_bytes, variant): (&[u8], ContentType)) {
233+
let mut decoder = Decoder::new(coap_code_bytes);
234+
assert_eq!(ContentType::decode(&mut decoder, &mut ()).unwrap(), variant);
235+
}
236+
237+
#[test_case(
238+
&[0x13];
239+
"application/ace+cbor"
240+
)]
241+
fn cbor_unsupported_coap_decoding_test(coap_code_bytes: &[u8]) {
242+
let mut decoder = Decoder::new(coap_code_bytes);
243+
assert!(ContentType::decode(&mut decoder, &mut ()).is_err());
153244
}
154245
}

rust/signed_doc/src/validator/rules/content_type.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,6 @@ impl ContentTypeRule {
6363
anyhow::bail!("Invalid {} content: {e}", self.exp)
6464
}
6565
},
66-
ContentType::Cddl => {
67-
// TODO: not implemented yet
68-
anyhow::bail!("`application/cddl` is valid but unavailable yet")
69-
},
7066
ContentType::Cbor => {
7167
let mut decoder = minicbor::Decoder::new(content);
7268

@@ -76,9 +72,18 @@ impl ContentTypeRule {
7672
anyhow::bail!("Unused bytes remain in the input after decoding")
7773
}
7874
},
79-
ContentType::JsonSchema => {
75+
ContentType::Cddl
76+
| ContentType::JsonSchema
77+
| ContentType::Css
78+
| ContentType::CssHandlebars
79+
| ContentType::Html
80+
| ContentType::HtmlHandlebars
81+
| ContentType::Markdown
82+
| ContentType::MarkdownHandlebars
83+
| ContentType::Plain
84+
| ContentType::PlainHandlebars => {
8085
// TODO: not implemented yet
81-
anyhow::bail!("`application/json+schema` is valid but unavailable yet")
86+
anyhow::bail!("`{}` is valid but unavailable yet", self.exp)
8287
},
8388
}
8489
Ok(())

rust/signed_doc/src/validator/rules/template.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,17 @@ impl ContentRule {
6565
};
6666
match template_content_type {
6767
ContentType::Json => templated_json_schema_check(doc, &template_doc),
68-
ContentType::Cddl | ContentType::Cbor | ContentType::JsonSchema => {
68+
ContentType::Cddl
69+
| ContentType::Cbor
70+
| ContentType::JsonSchema
71+
| ContentType::Css
72+
| ContentType::CssHandlebars
73+
| ContentType::Html
74+
| ContentType::HtmlHandlebars
75+
| ContentType::Markdown
76+
| ContentType::MarkdownHandlebars
77+
| ContentType::Plain
78+
| ContentType::PlainHandlebars => {
6979
// TODO: not implemented yet
7080
true
7181
},

0 commit comments

Comments
 (0)