Skip to content

Commit aa69d7b

Browse files
committed
fix(rust/signed_doc): ser/de for ContentType and ContentEncoding.
* update example to build docs from metadata file
1 parent e7f651f commit aa69d7b

File tree

5 files changed

+133
-29
lines changed

5 files changed

+133
-29
lines changed

rust/signed_doc/examples/mk_signed_doc.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::{
99
};
1010

1111
use clap::Parser;
12-
use coset::CborSerializable;
12+
use coset::{iana::CoapContentFormat, CborSerializable};
1313
use ed25519_dalek::{
1414
ed25519::signature::Signer,
1515
pkcs8::{DecodePrivateKey, DecodePublicKey},
@@ -121,6 +121,7 @@ impl Cli {
121121
let json_doc = load_json_from_file(&doc)?;
122122
let json_meta = load_json_from_file(&meta)
123123
.map_err(|e| anyhow::anyhow!("Failed to load metadata from file: {e}"))?;
124+
println!("{json_meta}");
124125
validate_json(&json_doc, &doc_schema)?;
125126
let compressed_doc = brotli_compress_json(&json_doc)?;
126127
let empty_cose_sign = build_empty_cose_doc(compressed_doc, &json_meta);
@@ -194,7 +195,7 @@ fn brotli_decompress_json(mut doc_bytes: &[u8]) -> anyhow::Result<serde_json::Va
194195

195196
fn cose_protected_header() -> coset::Header {
196197
coset::HeaderBuilder::new()
197-
.content_format(coset::iana::CoapContentFormat::Json)
198+
.content_format(CoapContentFormat::Json)
198199
.text_value(
199200
CONTENT_ENCODING_KEY.to_string(),
200201
CONTENT_ENCODING_VALUE.to_string().into(),
@@ -203,7 +204,19 @@ fn cose_protected_header() -> coset::Header {
203204
}
204205

205206
fn build_empty_cose_doc(doc_bytes: Vec<u8>, meta: &Metadata) -> coset::CoseSign {
206-
let mut protected_header = cose_protected_header();
207+
let mut builder = coset::HeaderBuilder::new();
208+
209+
if let Some(content_type) = meta.content_type() {
210+
builder = builder.content_format(CoapContentFormat::from(content_type));
211+
}
212+
213+
if let Some(content_encoding) = meta.content_encoding() {
214+
builder = builder.text_value(
215+
CONTENT_ENCODING_KEY.to_string(),
216+
format!("{content_encoding}").into(),
217+
);
218+
}
219+
let mut protected_header = builder.build();
207220

208221
protected_header.rest.push((
209222
coset::Label::Text("type".to_string()),

rust/signed_doc/meta.schema.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,19 @@
9797
},
9898
"section": {
9999
"type": "string"
100+
},
101+
"content-type": {
102+
"type": "string",
103+
"examples": [
104+
"json",
105+
"cbor"
106+
]
107+
},
108+
"content-encoding": {
109+
"type": "string",
110+
"examples": [
111+
"br"
112+
]
100113
}
101114
},
102115
"required": [

rust/signed_doc/src/metadata/content_encoding.rs

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,56 @@
11
//! Document Payload Content Encoding.
22
3+
use std::{
4+
fmt::{Display, Formatter},
5+
str::FromStr,
6+
};
7+
8+
use serde::{de, Deserialize, Deserializer};
9+
310
/// Catalyst Signed Document Content Encoding Key.
411
const CONTENT_ENCODING_KEY: &str = "Content-Encoding";
512

613
/// IANA `CoAP` Content Encoding.
7-
#[derive(Debug, serde::Deserialize)]
8-
#[serde(untagged)]
14+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
915
pub enum ContentEncoding {
1016
/// Brotli compression.format.
11-
#[serde(rename = "br")]
1217
Brotli,
1318
}
1419

20+
impl Display for ContentEncoding {
21+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
22+
match self {
23+
Self::Brotli => write!(f, "br"),
24+
}
25+
}
26+
}
27+
28+
impl FromStr for ContentEncoding {
29+
type Err = anyhow::Error;
30+
31+
fn from_str(encoding: &str) -> Result<Self, Self::Err> {
32+
match encoding {
33+
"br" => Ok(ContentEncoding::Brotli),
34+
_ => anyhow::bail!("Unsupported Content Encoding: {encoding:?}"),
35+
}
36+
}
37+
}
38+
39+
impl<'de> Deserialize<'de> for ContentEncoding {
40+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
41+
where D: Deserializer<'de> {
42+
let s = String::deserialize(deserializer)?;
43+
FromStr::from_str(&s).map_err(de::Error::custom)
44+
}
45+
}
46+
1547
impl TryFrom<&coset::cbor::Value> for ContentEncoding {
1648
type Error = anyhow::Error;
1749

18-
#[allow(clippy::todo)]
1950
fn try_from(val: &coset::cbor::Value) -> anyhow::Result<ContentEncoding> {
2051
match val.as_text() {
21-
Some(encoding) => {
22-
match encoding.to_string().to_lowercase().as_ref() {
23-
"br" => Ok(ContentEncoding::Brotli),
24-
_ => anyhow::bail!("Unsupported Content Encoding: {encoding}"),
25-
}
26-
},
27-
_ => {
52+
Some(encoding) => encoding.parse(),
53+
None => {
2854
anyhow::bail!("Expected Content Encoding to be a string");
2955
},
3056
}
Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,69 @@
11
//! Document Payload Content Type.
22
3+
use std::{
4+
fmt::{Display, Formatter},
5+
str::FromStr,
6+
};
7+
8+
use coset::iana::CoapContentFormat;
9+
use serde::{de, Deserialize, Deserializer};
10+
311
/// Payload Content Type.
4-
#[derive(Debug, serde::Deserialize)]
5-
#[serde(untagged, rename_all_fields = "lowercase")]
12+
#[derive(Copy, Clone, Debug)]
613
pub enum ContentType {
714
/// 'application/cbor'
815
Cbor,
916
/// 'application/json'
1017
Json,
1118
}
1219

20+
impl Display for ContentType {
21+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
22+
match self {
23+
Self::Cbor => write!(f, "cbor"),
24+
Self::Json => write!(f, "json"),
25+
}
26+
}
27+
}
28+
29+
impl FromStr for ContentType {
30+
type Err = anyhow::Error;
31+
32+
fn from_str(s: &str) -> Result<Self, Self::Err> {
33+
match s {
34+
"cbor" => Ok(Self::Cbor),
35+
"json" => Ok(Self::Json),
36+
_ => anyhow::bail!("Unsupported Content Type: {s:?}"),
37+
}
38+
}
39+
}
40+
41+
impl<'de> Deserialize<'de> for ContentType {
42+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
43+
where D: Deserializer<'de> {
44+
let s = String::deserialize(deserializer)?;
45+
FromStr::from_str(&s).map_err(de::Error::custom)
46+
}
47+
}
48+
49+
impl From<ContentType> for CoapContentFormat {
50+
fn from(value: ContentType) -> Self {
51+
match value {
52+
ContentType::Cbor => Self::Cbor,
53+
ContentType::Json => Self::Json,
54+
}
55+
}
56+
}
57+
1358
impl TryFrom<&coset::ContentType> for ContentType {
1459
type Error = anyhow::Error;
1560

1661
fn try_from(value: &coset::ContentType) -> Result<Self, Self::Error> {
17-
use coset::iana::CoapContentFormat as Format;
18-
match value {
19-
coset::ContentType::Assigned(Format::Json) => Ok(ContentType::Json),
20-
coset::ContentType::Assigned(Format::Cbor) => Ok(ContentType::Cbor),
62+
let content_type = match value {
63+
coset::ContentType::Assigned(CoapContentFormat::Json) => ContentType::Json,
64+
coset::ContentType::Assigned(CoapContentFormat::Cbor) => ContentType::Cbor,
2165
_ => anyhow::bail!("Unsupported Content Type {value:?}"),
22-
}
66+
};
67+
Ok(content_type)
2368
}
2469
}

rust/signed_doc/src/metadata/mod.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ pub struct Metadata {
4242
/// Reference to the document section.
4343
section: Option<String>,
4444
/// Document Payload Content Type.
45+
#[serde(default, rename = "content-type")]
4546
content_type: Option<ContentType>,
4647
/// Document Payload Content Encoding.
48+
#[serde(default, rename = "content-encoding")]
4749
content_encoding: Option<ContentEncoding>,
4850
/// Metadata Content Errors
4951
#[serde(skip)]
@@ -105,10 +107,16 @@ impl Metadata {
105107
&self.content_errors
106108
}
107109

108-
/// Return
110+
/// Returns the Document Content Type, if any.
109111
#[must_use]
110-
pub fn content_type(&self) -> &Option<ContentType> {
111-
&self.content_type
112+
pub fn content_type(&self) -> Option<ContentType> {
113+
self.content_type
114+
}
115+
116+
/// Returns the Document Content Encoding, if any.
117+
#[must_use]
118+
pub fn content_encoding(&self) -> Option<ContentEncoding> {
119+
self.content_encoding
112120
}
113121
}
114122

@@ -194,11 +202,10 @@ impl From<&coset::ProtectedHeader> for Metadata {
194202
},
195203
}
196204
},
197-
_ => {
198-
errors.push(
199-
"Invalid COSE document protected header '{CONTENT_ENCODING_KEY}' label"
200-
.to_string(),
201-
);
205+
None => {
206+
errors.push(format!(
207+
"Invalid COSE document protected header '{CONTENT_ENCODING_KEY}' is missing"
208+
));
202209
},
203210
}
204211
match cose_protected_header_find(protected, "type") {

0 commit comments

Comments
 (0)