Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support JSON payloads #40

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ base64-url = { version = "2.0.2" }
ipld-core = { version = "0.4" }
serde_ipld_dagjson = { version = "0.2", default-features = false, optional = true }
serde_ipld_dagcbor = "0.6"
serde_json = "1"
serde = "1"
serde_derive = "1"
thiserror = "1"
Expand Down
14 changes: 13 additions & 1 deletion src/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,18 @@ impl TryFrom<Encoded> for JsonWebSignature {
type Error = Error;

fn try_from(value: Encoded) -> Result<Self, Self::Error> {
let link = Cid::try_from(value.payload.as_ref().ok_or(Error::NotJws)?.as_slice())?;
let payload = value.payload.as_ref().ok_or(Error::NotJws)?;

let (link, pld) = match serde_json::from_slice::<serde_json::Value>(payload.as_ref()) {
Ok(json) => {
let res = match crate::JsonPld(json).try_into().map_err(|_| Error::NotJws)? {
Ipld::Map(map) => map,
_ => return Err(Error::NotJws),
};
(None, Some(res))
}
Err(_) => (Some(Cid::try_from(payload.as_ref())?), None),
};
Ok(Self {
payload: value
.payload
Expand All @@ -104,6 +115,7 @@ impl TryFrom<Encoded> for JsonWebSignature {
.map(Signature::from)
.collect(),
link,
pld,
})
}
}
Expand Down
66 changes: 54 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ use std::collections::BTreeMap;
use ipld_core::{
cid::Cid,
codec::{Codec, Links},
ipld,
ipld::Ipld,
};
use serde_derive::Serialize;
Expand Down Expand Up @@ -152,22 +151,62 @@ impl Codec<Jose> for DagJsonCodec {
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct JsonWebSignature {
/// CID link from the payload.
pub link: Cid,

#[serde(skip_serializing_if = "Option::is_none")]
pub link: Option<Cid>,
/// The payload base64 url encoded.
pub payload: String,

#[serde(skip_serializing_if = "Option::is_none")]
/// The json payload with ipfs:// prefixed strings replaced with CIDs.
pub pld: Option<BTreeMap<String, Ipld>>,
/// The set of signatures.
pub signatures: Vec<Signature>,
}

impl<'a> From<&'a JsonWebSignature> for Ipld {
fn from(value: &'a JsonWebSignature) -> Self {
ipld!({
"payload": value.payload.to_owned(),
"signatures": value.signatures.iter().map(Ipld::from).collect::<Vec<Ipld>>(),
"link": value.link,
})
#[derive(Clone, Debug, PartialEq)]
/// Wrapper around a json value to convert ipfs:// prefixed strings to CIDs.
struct JsonPld(serde_json::Value);

impl TryFrom<JsonPld> for Ipld {
type Error = anyhow::Error;

fn try_from(value: JsonPld) -> Result<Self, Self::Error> {
// can we use a serde_ipld_* crate or something to handle most of this for for us?
match value.0 {
serde_json::Value::Null => Ok(Ipld::Null),
serde_json::Value::Bool(b) => Ok(Ipld::Bool(b)),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Ok(Ipld::Integer(i as i128))
} else if let Some(f) = n.as_f64() {
// should we reject NaN and infinities?
Ok(Ipld::Float(f))
} else {
anyhow::bail!("Invalid number. Should be unreachable")
}
}
serde_json::Value::String(s) => {
if let Some(str) = s.strip_prefix("ipfs://") {
let cid = Cid::try_from(str)?;
Ok(Ipld::Link(cid))
} else {
Ok(Ipld::String(s))
}
}
serde_json::Value::Array(arr) => {
let list = arr
.into_iter()
.map(|v| JsonPld(v).try_into())
.collect::<Result<Vec<_>, _>>()?;
Ok(Ipld::List(list))
}
serde_json::Value::Object(obj) => {
let map = obj
.into_iter()
.map(|(k, v)| JsonPld(v).try_into().map(|v| (k, v)))
.collect::<Result<BTreeMap<_, _>, _>>()?;
Ok(Ipld::Map(map))
}
}
}
}

Expand Down Expand Up @@ -356,6 +395,8 @@ impl<'a> From<&'a Recipient> for Ipld {
mod tests {
use std::collections::BTreeMap;

use ipld_core::ipld;

use super::*;

struct JwsFixture {
Expand Down Expand Up @@ -425,7 +466,7 @@ mod tests {
} = fixture_jws();
let (payload_b64, protected_b64, signature_b64) =
fixture_jws_base64(&payload, &protected, &signature);
let link = Cid::try_from(base64_url::decode(&payload_b64).unwrap()).unwrap();
let link = Some(Cid::try_from(base64_url::decode(&payload_b64).unwrap()).unwrap());
assert_roundtrip(
DagJoseCodec,
&JsonWebSignature {
Expand All @@ -439,6 +480,7 @@ mod tests {
signature: signature_b64,
}],
link,
pld: None,
},
&ipld!({
"payload": payload,
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ test_fixture!(jws, "jws");
test_fixture!(jws_signature_1, "jws-signature-1");
test_fixture!(jws_signature_2, "jws-signature-2");
test_fixture!(jws_signatures, "jws-signatures");
test_fixture!(jws_signature_pld, "jws-signature-pld");
test_fixture!(jwe_symmetric, "jwe-symmetric");
test_fixture!(jwe_asymmetric, "jwe-asymmetric");
test_fixture!(jwe_no_recipients, "jwe-no-recipients");
Expand Down
110 changes: 101 additions & 9 deletions tests/fixtures/dag-jose.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Fixtures

### JWS

This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):
This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):

[testmark]:# (jws/serial.dag-jose.cid)
```
Expand Down Expand Up @@ -74,7 +74,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/)

### JWS with one signature

This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):
This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):

[testmark]:# (jws-signature-1/serial.dag-jose.cid)
```
Expand Down Expand Up @@ -126,7 +126,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/)

### JWS with another signature

This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):
This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):

[testmark]:# (jws-signature-2/serial.dag-jose.cid)
```
Expand Down Expand Up @@ -178,7 +178,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/)

### JWS with multiple signatures

This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):
This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):

[testmark]:# (jws-signatures/serial.dag-jose.cid)
```
Expand Down Expand Up @@ -237,9 +237,101 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/)
}
```

### JWS with payload

This is the base32-encoded JSON payload for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):

[testmark]:# (jws-signature-pld/serial.dag-jose.cid)
```
bagcqceras6vcqjafsrhwfsgxmzd6g5c2vm3mfolbbubg5rfuhhnobay4m2vq
```

This is a DAG-JOSE object, in hexadecimal:

[testmark]:# (jws-signature-pld/serial.dag-jose.hex)
```
a2677061796c6f61645901717b2274657374223a227061796c6f6164222c22614c696e6b223a2269
7066733a2f2f6261667962656967367876356e777068666d76636e656b74706e6f6a747333336a71
6375616d37626d7965327062353461646e727463636a6c7375222c22617272223a5b22697066733a
2f2f6261667962656967367876356e777068666d76636e656b74706e6f6a747333336a716375616d
37626d7965327062353461646e727463636a6c7375222c226974656d31222c226974656d32225d2c
226e6573746564223a7b22614c696e6b223a22697066733a2f2f6261667962656967367876356e77
7068666d76636e656b74706e6f6a747333336a716375616d37626d7965327062353461646e727463
636a6c7375222c22617272223a5b22697066733a2f2f6261667962656967367876356e777068666d
76636e656b74706e6f6a747333336a716375616d37626d7965327062353461646e727463636a6c73
75222c226974656d31222c226974656d32225d7d7d6a7369676e61747572657381a26970726f7465
63746564507b22616c67223a2245533235364b227d697369676e61747572655840218f001e1401d2
7e50a4ed68a6c7bcddde87ad759fa1f3e35ba89fe6f541ec3932df08f6f9d693f08d711d7e6ce0f6
ee0b7a30668dabafc0be9c642c7ed1c4fb
```

When it is parsed, we should see these paths within the data
when we walk over it at the [data model](/docs/data-model/) level:

[testmark]:# (jws-signature-pld/paths)
```text
payload
pld
pld/aLink
pld/arr
pld/arr/0
pld/arr/1
pld/arr/2
pld/nested/aLink
pld/nested/arr
pld/nested/arr/0
pld/nested/arr/1
pld/nested/arr/2
signatures
signatures/0
signatures/0/protected
signatures/0/signature
```

If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/)
(and prettyprint it), we should get this result:

[testmark]:# (jws-signature-pld/datamodel.dag-json.pretty)
```json
{
"payload": "eyJ0ZXN0IjoicGF5bG9hZCIsImFMaW5rIjoiaXBmczovL2JhZnliZWlnNnh2NW53cGhmbXZjbmVrdHBub2p0czMzanFjdWFtN2JteWUycGI1NGFkbnJ0Y2NqbHN1IiwiYXJyIjpbImlwZnM6Ly9iYWZ5YmVpZzZ4djVud3BoZm12Y25la3Rwbm9qdHMzM2pxY3VhbTdibXllMnBiNTRhZG5ydGNjamxzdSIsIml0ZW0xIiwiaXRlbTIiXSwibmVzdGVkIjp7ImFMaW5rIjoiaXBmczovL2JhZnliZWlnNnh2NW53cGhmbXZjbmVrdHBub2p0czMzanFjdWFtN2JteWUycGI1NGFkbnJ0Y2NqbHN1IiwiYXJyIjpbImlwZnM6Ly9iYWZ5YmVpZzZ4djVud3BoZm12Y25la3Rwbm9qdHMzM2pxY3VhbTdibXllMnBiNTRhZG5ydGNjamxzdSIsIml0ZW0xIiwiaXRlbTIiXX19",
"pld": {
"test": "payload",
"aLink": {
"/": "bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu"
},
"arr": [
{
"/": "bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu"
},
"item1",
"item2"
],
"nested": {
"aLink": {
"/": "bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu"
},
"arr": [
{
"/": "bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu"
},
"item1",
"item2"
]
}
},
"signatures": [
{
"protected": "eyJhbGciOiJFUzI1NksifQ",
"signature": "IY8AHhQB0n5QpO1opse83d6HrXWfofPjW6if5vVB7Dky3wj2-daT8I1xHX5s4PbuC3owZo2rr8C-nGQsftHE-w"
}
]
}
```

### JWE symmetric

This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):
This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):

[testmark]:# (jwe-symmetric/serial.dag-jose.cid)
```
Expand Down Expand Up @@ -281,7 +373,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/)

### JWE asymmetric

This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):
This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):

[testmark]:# (jwe-asymmetric/serial.dag-jose.cid)
```
Expand Down Expand Up @@ -339,7 +431,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/)

### JWE with no recipients

This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):
This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):

[testmark]:# (jwe-no-recipients/serial.dag-jose.cid)
```
Expand Down Expand Up @@ -382,7 +474,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/)

### JWE with one recipient

This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):
This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):

[testmark]:# (jwe-recipient/serial.dag-jose.cid)
```
Expand Down Expand Up @@ -456,7 +548,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/)

### JWE with multiple recipients

This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):
This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12):

[testmark]:# (jwe-recipients/serial.dag-jose.cid)
```
Expand Down
Loading