Make `Doc` type payload generic

This commit is contained in:
Alexis Sellier 2022-12-08 15:38:09 +01:00
parent e7f2637126
commit 98c1566109
No known key found for this signature in database
20 changed files with 336 additions and 224 deletions

131
Cargo.lock generated
View File

@ -30,9 +30,9 @@ dependencies = [
[[package]]
name = "amplify"
version = "4.0.0-beta.4"
version = "4.0.0-beta.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62c3b7fe483c11f7a434ec6923a68749b3415c1328857f1a8e37bb2c792a25c0"
checksum = "4ff4d466b0cb5124991d3a2085c28f2aed6d86264c3c0bdbd7d1b64a49b4726b"
dependencies = [
"amplify_derive",
"amplify_num",
@ -543,9 +543,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.82"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453"
checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf"
dependencies = [
"cc",
"cxxbridge-flags",
@ -555,9 +555,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.82"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0"
checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39"
dependencies = [
"cc",
"codespan-reporting",
@ -570,15 +570,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.82"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71"
checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12"
[[package]]
name = "cxxbridge-macro"
version = "1.0.82"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470"
checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6"
dependencies = [
"proc-macro2",
"quote",
@ -597,9 +597,9 @@ dependencies = [
[[package]]
name = "data-encoding"
version = "2.3.2"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]]
name = "data-encoding-macro"
@ -623,9 +623,9 @@ dependencies = [
[[package]]
name = "der"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f"
checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
dependencies = [
"const-oid",
"pem-rfc7468",
@ -704,9 +704,9 @@ dependencies = [
[[package]]
name = "ed25519-compact"
version = "2.0.2"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f2d21333b679bbbac680b3eb45c86937e42f69277028f4e97b599b80b86c253"
checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c"
dependencies = [
"ct-codecs",
"getrandom 0.2.8",
@ -873,9 +873,9 @@ dependencies = [
[[package]]
name = "filetime"
version = "0.2.18"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3"
checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9"
dependencies = [
"cfg-if",
"libc",
@ -1020,13 +1020,31 @@ dependencies = [
"thiserror",
]
[[package]]
name = "git-ref-format"
version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481#39ce2f934915a563f9420ac9c85480df8a591481"
dependencies = [
"git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"git-ref-format-macro 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
]
[[package]]
name = "git-ref-format"
version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d"
dependencies = [
"git-ref-format-core",
"git-ref-format-macro",
"git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
"git-ref-format-macro 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
]
[[package]]
name = "git-ref-format-core"
version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481#39ce2f934915a563f9420ac9c85480df8a591481"
dependencies = [
"serde",
"thiserror",
]
[[package]]
@ -1038,12 +1056,23 @@ dependencies = [
"thiserror",
]
[[package]]
name = "git-ref-format-macro"
version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481#39ce2f934915a563f9420ac9c85480df8a591481"
dependencies = [
"git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"proc-macro-error",
"quote",
"syn",
]
[[package]]
name = "git-ref-format-macro"
version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d"
dependencies = [
"git-ref-format-core",
"git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
"proc-macro-error",
"quote",
"syn",
@ -1467,9 +1496,9 @@ checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
[[package]]
name = "libc"
version = "0.2.137"
version = "0.2.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
[[package]]
name = "libgit2-sys"
@ -2067,7 +2096,7 @@ dependencies = [
"cyphernet",
"ed25519-compact",
"fastrand",
"git-ref-format",
"git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"git2",
"log",
"multibase",
@ -2126,7 +2155,7 @@ dependencies = [
"ed25519-compact",
"fastrand",
"git-commit",
"git-ref-format",
"git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"git-trailers",
"git2",
"log",
@ -2164,7 +2193,7 @@ dependencies = [
"cyphernet",
"ed25519-compact",
"fastrand",
"git-ref-format",
"git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"multibase",
"qcheck",
"qcheck-macros",
@ -2183,7 +2212,7 @@ name = "radicle-git-ext"
version = "0.2.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d"
dependencies = [
"git-ref-format",
"git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
"git2",
"percent-encoding",
"radicle-std-ext",
@ -2229,7 +2258,7 @@ dependencies = [
"colored",
"crossbeam-channel",
"fastrand",
"git-ref-format",
"git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"lexopt",
"log",
"nakamoto-net",
@ -2281,7 +2310,7 @@ dependencies = [
"base64",
"either",
"flate2",
"git-ref-format",
"git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
"git2",
"nom 6.1.2",
"nonempty 0.5.0",
@ -2297,7 +2326,7 @@ name = "radicle-tools"
version = "0.2.0"
dependencies = [
"anyhow",
"git-ref-format",
"git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"radicle",
]
@ -2489,9 +2518,9 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
[[package]]
name = "rustix"
version = "0.36.4"
version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23"
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
dependencies = [
"bitflags",
"errno",
@ -2524,9 +2553,9 @@ dependencies = [
[[package]]
name = "scale-info"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d8a765117b237ef233705cc2cc4c6a27fccd46eea6ef0c8c6dae5f3ef407f8"
checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608"
dependencies = [
"cfg-if",
"derive_more",
@ -2536,9 +2565,9 @@ dependencies = [
[[package]]
name = "scale-info-derive"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdcd47b380d8c4541044e341dcd9475f55ba37ddc50c908d945fc036a8642496"
checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@ -2580,18 +2609,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.148"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc"
checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.148"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c"
checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4"
dependencies = [
"proc-macro2",
"quote",
@ -2901,9 +2930,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.104"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce"
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
dependencies = [
"proc-macro2",
"quote",
@ -3077,9 +3106,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.22.0"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3"
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
dependencies = [
"autocfg",
"bytes",
@ -3090,7 +3119,7 @@ dependencies = [
"pin-project-lite",
"socket2",
"tokio-macros",
"winapi",
"windows-sys",
]
[[package]]
@ -3145,9 +3174,9 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba"
checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
dependencies = [
"bitflags",
"bytes",
@ -3258,9 +3287,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "typenum"
version = "1.15.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "uint"
@ -3556,9 +3585,9 @@ dependencies = [
[[package]]
name = "zeroize_derive"
version = "1.3.2"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17"
checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
dependencies = [
"proc-macro2",
"quote",

View File

@ -42,4 +42,4 @@ rev = "d3115a22158c8395705babefdc89049f7510d32d"
[patch.crates-io.git-ref-format]
git = "https://github.com/radicle-dev/radicle-git"
rev = "d3115a22158c8395705babefdc89049f7510d32d"
rev = "39ce2f934915a563f9420ac9c85480df8a591481"

View File

@ -15,7 +15,7 @@ ok Project heartwood created
}
Your project id is rad:z2TBtGrJKGsremYAPec6vN4n77Ba7. You can show it any time by running:
Your project id is rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji. You can show it any time by running:
rad .
To publish your project to the network, run:
@ -27,5 +27,5 @@ Projects can be listed with the `ls` command:
```
$ rad ls
heartwood rad:z2TBtGrJKGsremYAPec6vN4n77Ba7 cdf76ce Radicle Heartwood Protocol & Stack
heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji cdf76ce Radicle Heartwood Protocol & Stack
```

View File

@ -79,12 +79,11 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
let id = options.id;
let storage = &profile.storage;
let Doc {
payload, delegates, ..
} = storage
let doc = storage
.repository(id)?
.project_of(profile.id())
.identity_of(profile.id())
.context("project could not be found in local storage")?;
let payload = doc.project()?;
let path = PathBuf::from(payload.name.clone());
if path.exists() {
@ -109,7 +108,8 @@ pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
};
spinner.finish();
let remotes = delegates
let remotes = doc
.delegates
.into_iter()
.map(|did| *did)
.filter(|id| id != profile.id())

View File

@ -93,10 +93,11 @@ pub fn clone(id: Id, _interactive: Interactive, ctx: impl term::Context) -> anyh
let doc = profile
.storage
.repository(id)?
.project_of(profile.id())
.identity_of(profile.id())
.map_err(|_e| anyhow!("couldn't load project {} from local state", id))?;
let proj = doc.project()?;
let path = Path::new(&doc.name);
let path = Path::new(&proj.name);
let repo = rad::checkout(id, profile.id(), path, &profile.storage)?;
let delegates = doc
.delegates
@ -104,7 +105,7 @@ pub fn clone(id: Id, _interactive: Interactive, ctx: impl term::Context) -> anyh
.map(|d| **d)
.filter(|id| id != profile.id())
.collect::<Vec<_>>();
let default_branch = doc.payload.default_branch.clone();
let default_branch = proj.default_branch.clone();
// Setup tracking for project delegates.
setup_remotes(

View File

@ -201,24 +201,26 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
&profile.storage,
) {
Ok((id, doc, _)) => {
let proj = doc.project()?;
spinner.message(format!(
"Project {} created",
term::format::highlight(&doc.name)
term::format::highlight(&proj.name)
));
spinner.finish();
if interactive.no() {
term::blob(json::to_string_pretty(&doc.payload)?);
term::blob(json::to_string_pretty(&proj)?);
term::blank();
}
if options.set_upstream || git::branch_remote(&repo, &doc.default_branch).is_err() {
if options.set_upstream || git::branch_remote(&repo, &proj.default_branch).is_err() {
// Setup eg. `master` -> `rad/master`
radicle::git::set_upstream(
&repo,
&radicle::rad::REMOTE_NAME,
&doc.default_branch,
&radicle::git::refs::workdir::branch(&doc.default_branch),
&proj.default_branch,
&radicle::git::refs::workdir::branch(&proj.default_branch),
)?;
}

View File

@ -50,13 +50,13 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
storage.projects()?.into_iter().for_each(|id| {
let Ok(repo) = storage.repository(id) else { return };
let Ok((_, head)) = repo.head() else { return };
let Ok(Doc { payload, .. }) = repo.project_of(profile.id()) else { return };
let Ok(Project { name, description, .. }) = repo.project_of(profile.id()) else { return };
let head = term::format::oid(head);
table.push([
term::format::bold(payload.name),
term::format::bold(name),
term::format::tertiary(id),
term::format::secondary(head),
term::format::italic(payload.description),
term::format::italic(description),
]);
});
table.render();

View File

@ -137,7 +137,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let signer = term::signer(&profile)?;
let repository = profile.storage.repository(id)?;
let _project = repository
.project_of(profile.id())
.identity_of(profile.id())
.context(format!("couldn't load project {} from local state", id))?;
let repository = profile.storage.repository(id)?;
let mut patches = Patches::open(*profile.id(), &repository)?;

View File

@ -137,7 +137,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let signer = term::signer(&profile)?;
let repository = profile.storage.repository(id)?;
let _project = repository
.project_of(profile.id())
.identity_of(profile.id())
.context(format!("couldn't load project {} from local state", id))?;
let mut patches = Patches::open(*profile.id(), &repository)?;

View File

@ -3,8 +3,7 @@ use std::str::FromStr;
use anyhow::{anyhow, Context as _};
use radicle::node::Handle;
use radicle::prelude::*;
use radicle::node::{Handle, NodeId};
use radicle::storage::WriteStorage;
use crate::terminal as term;
@ -94,12 +93,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;
let storage = &profile.storage;
let (_, rid) = radicle::rad::cwd().context("this command must be run within a project")?;
let Doc { payload, .. } = storage.repository(rid)?.project_of(profile.id())?;
let project = storage.repository(rid)?.project_of(profile.id())?;
let mut node = radicle::node::connect(&profile.node())?;
term::info!(
"Establishing 🌱 tracking relationship for {}",
term::format::highlight(&payload.name)
term::format::highlight(&project.name)
);
term::blank();

View File

@ -68,18 +68,18 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
.context("current directory is not a git repository; please supply an `<id>`")?;
let profile = ctx.profile()?;
let storage = &profile.storage;
let Doc { payload, .. } = storage.repository(id)?.project_of(profile.id())?;
let project = storage.repository(id)?.project_of(profile.id())?;
if untrack(id, &profile)? {
term::success!(
"Tracking relationships for {} ({}) removed",
term::format::highlight(payload.name),
term::format::highlight(project.name),
&id.to_human()
);
} else {
term::info!(
"Tracking relationships for {} ({}) doesn't exist",
term::format::highlight(payload.name),
term::format::highlight(project.name),
&id.to_human()
);
}

View File

@ -19,7 +19,7 @@ git-trailers = { version = "0.1" }
log = { version = "0.4.17" }
nonempty = { version = "0.8.1", features = ["serialize"] }
petgraph = { version = "0.5" }
radicle-git-ext = { version = "0.2" }
radicle-git-ext = { version = "0" }
serde_json = { version = "1.0" }
thiserror = { version = "1.0" }

View File

@ -17,7 +17,7 @@ use tower_http::trace::TraceLayer;
use tracing::Span;
use radicle::cob::issue::Issues;
use radicle::identity::{Doc, Id};
use radicle::identity::Id;
use radicle::storage::{ReadRepository, WriteStorage};
use radicle::Profile;
@ -49,7 +49,7 @@ impl Context {
let storage = &self.profile.storage;
let repo = storage.repository(id)?;
let (_, head) = repo.head()?;
let Doc { payload, .. } = repo.project_of(self.profile.id())?;
let payload = repo.project_of(self.profile.id())?;
let issues = (Issues::open(self.profile.public_key, &repo)?).count()?;
Ok(project::Info {
@ -142,7 +142,7 @@ pub struct PaginationQuery {
mod project {
use radicle::git::Oid;
use radicle::identity::project::Payload;
use radicle::identity::project::Project;
use radicle::identity::Id;
use serde::Serialize;
@ -152,7 +152,7 @@ mod project {
pub struct Info {
/// Project metadata.
#[serde(flatten)]
pub payload: Payload,
pub payload: Project,
pub head: Oid,
pub patches: usize,
pub issues: usize,

View File

@ -3,7 +3,7 @@ use axum::routing::get;
use axum::{Extension, Json, Router};
use radicle::cob::issue::Issues;
use radicle::identity::{Did, Doc};
use radicle::identity::Did;
use radicle::storage::{ReadRepository, WriteStorage};
use crate::api::axum_extra::{Path, Query};
@ -38,9 +38,10 @@ async fn delegates_projects_handler(
.filter_map(|id| {
let Ok(repo) = storage.repository(id) else { return None };
let Ok((_, head)) = repo.head() else { return None };
let Ok(Doc { payload, delegates, .. }) = repo.project_of(ctx.profile.id()) else { return None };
let Ok(doc) = repo.identity_of(ctx.profile.id()) else { return None };
let Ok(payload) = doc.project() else { return None };
if !delegates.iter().any(|d| *d == delegate) {
if !doc.delegates.iter().any(|d| *d == delegate) {
return None;
}

View File

@ -14,7 +14,7 @@ use radicle::cob::issue::Issues;
use radicle::cob::thread::{self, CommentId};
use radicle::cob::Timestamp;
use radicle::git::raw::BranchType;
use radicle::identity::{Doc, Id, PublicKey};
use radicle::identity::{Id, PublicKey};
use radicle::node::NodeId;
use radicle::storage::{Oid, ReadRepository, WriteRepository, WriteStorage};
use radicle_surf::git::History;
@ -68,7 +68,7 @@ async fn project_root_handler(
.filter_map(|id| {
let Ok(repo) = storage.repository(id) else { return None };
let Ok((_, head)) = repo.head() else { return None };
let Ok(Doc { payload, .. }) = repo.project_of(ctx.profile.id()) else { return None };
let Ok(payload) = repo.project_of(ctx.profile.id()) else { return None };
let Ok(issues) = Issues::open(ctx.profile.public_key, &repo) else { return None };
let Ok(issues) = (*issues).count() else { return None };

View File

@ -1,7 +1,7 @@
mod id;
use std::collections::{BTreeMap, HashMap};
use std::fmt::Write as _;
use std::fmt::{self, Write as _};
use std::io;
use std::marker::PhantomData;
use std::ops::Deref;
@ -46,6 +46,8 @@ pub enum DocError {
Io(#[from] io::Error),
#[error("verification: {0}")]
Verification(#[from] VerificationError),
#[error("payload: {0}")]
Payload(#[from] PayloadError),
#[error("git: {0}")]
Git(#[from] git::Error),
#[error("git: {0}")]
@ -68,26 +70,120 @@ impl DocError {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Payload {
pub name: String,
pub description: String,
pub default_branch: git::RefString,
#[derive(Debug, Error)]
pub enum ProjectError {
#[error("invalid name: {0}")]
Name(&'static str),
#[error("invalid description: {0}")]
Description(&'static str),
#[error("invalid default branch: {0}")]
DefaultBranch(&'static str),
#[error("json: {0}")]
Json(#[from] serde_json::Error),
#[error("project payload not found in identity document")]
NotFound,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Project {
pub name: String,
pub description: String,
pub default_branch: BranchName,
}
impl From<Project> for Payload {
fn from(proj: Project) -> Self {
let value = serde_json::to_value(proj)
.expect("Payload::from: could not convert project into value");
Self { value }
}
}
impl Project {
/// Validate the project data.
pub fn validate(&self) -> Result<(), ProjectError> {
if self.name.is_empty() {
return Err(ProjectError::Name("name cannot be empty"));
}
if self.name.len() > MAX_STRING_LENGTH {
return Err(ProjectError::Name("name cannot exceed 255 bytes"));
}
if self.description.len() > MAX_STRING_LENGTH {
return Err(ProjectError::Description(
"description cannot exceed 255 bytes",
));
}
if self.default_branch.is_empty() {
return Err(ProjectError::DefaultBranch(
"default branch cannot be empty",
));
}
if self.default_branch.len() > MAX_STRING_LENGTH {
return Err(ProjectError::DefaultBranch(
"default branch cannot exceed 255 bytes",
));
}
Ok(())
}
}
#[derive(Debug, Error)]
pub enum PayloadError {
#[error("json: {0}")]
Json(#[from] serde_json::Error),
#[error("payload '{0}' not found in identity document")]
NotFound(PayloadId),
}
/// Identifies an identity document payload type.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
// TODO: Restrict values.
pub struct Namespace(String);
pub struct PayloadId(String);
impl fmt::Display for PayloadId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl PayloadId
where
PayloadId: Clone,
{
/// Project payload type.
pub fn project() -> Self {
Self(String::from("xyz.radicle.project"))
}
}
/// Payload value.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Payload {
value: serde_json::Value,
}
impl From<serde_json::Value> for Payload {
fn from(value: serde_json::Value) -> Self {
Self { value }
}
}
impl Deref for Payload {
type Target = serde_json::Value;
fn deref(&self) -> &Self::Target {
&self.value
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Doc<V> {
#[serde(rename = "xyz.radicle.project")]
pub payload: Payload,
#[serde(flatten)]
pub extensions: BTreeMap<Namespace, serde_json::Value>,
pub payload: BTreeMap<PayloadId, Payload>,
pub delegates: NonEmpty<Did>,
pub threshold: usize,
@ -117,6 +213,17 @@ impl Doc<Verified> {
false
}
/// Get the project payload, if it exists and is valid, out of this document.
pub fn project(&self) -> Result<Project, PayloadError> {
let value = self
.payload
.get(&PayloadId::project())
.ok_or_else(|| PayloadError::NotFound(PayloadId::project()))?;
let proj: Project = serde_json::from_value((**value).clone())?;
Ok(proj)
}
pub fn sign<G: crypto::Signer>(&self, signer: &G) -> Result<(git::Oid, Signature), DocError> {
let (oid, bytes) = self.encode()?;
let sig = signer.sign(&bytes);
@ -181,66 +288,27 @@ impl Doc<Verified> {
}
}
impl<V> Deref for Doc<V> {
type Target = Payload;
fn deref(&self) -> &Self::Target {
&self.payload
}
}
#[derive(Error, Debug)]
pub enum VerificationError {
#[error("invalid name: {0}")]
Name(&'static str),
#[error("invalid description: {0}")]
Description(&'static str),
#[error("invalid default branch: {0}")]
DefaultBranch(&'static str),
#[error("invalid delegates: {0}")]
Delegates(&'static str),
#[error("invalid version `{0}`")]
Version(u32),
#[error("invalid parent: {0}")]
Parent(&'static str),
#[error("invalid threshold `{0}`: {1}")]
Threshold(usize, &'static str),
}
impl Doc<Unverified> {
pub fn initial(
name: String,
description: String,
default_branch: BranchName,
delegate: Did,
) -> Self {
Self {
payload: Payload {
name,
description,
default_branch,
},
extensions: BTreeMap::new(),
delegates: NonEmpty::new(delegate),
threshold: 1,
verified: PhantomData,
}
pub fn initial(project: Project, delegate: Did) -> Self {
Self::new(project, NonEmpty::new(delegate), 1)
}
pub fn new(
name: String,
description: String,
default_branch: BranchName,
delegates: NonEmpty<Did>,
threshold: usize,
) -> Self {
pub fn new(project: Project, delegates: NonEmpty<Did>, threshold: usize) -> Self {
let project =
serde_json::to_value(project).expect("Doc::initial: payload must be serializable");
Self {
payload: Payload {
name,
description,
default_branch,
},
extensions: BTreeMap::new(),
payload: BTreeMap::from_iter([(PayloadId::project(), Payload::from(project))]),
delegates,
threshold,
verified: PhantomData,
@ -252,17 +320,6 @@ impl Doc<Unverified> {
}
pub fn verified(self) -> Result<Doc<Verified>, VerificationError> {
if self.name.is_empty() {
return Err(VerificationError::Name("name cannot be empty"));
}
if self.name.len() > MAX_STRING_LENGTH {
return Err(VerificationError::Name("name cannot exceed 255 bytes"));
}
if self.description.len() > MAX_STRING_LENGTH {
return Err(VerificationError::Description(
"description cannot exceed 255 bytes",
));
}
if self.delegates.len() > MAX_DELEGATES {
return Err(VerificationError::Delegates(
"number of delegates cannot exceed 255",
@ -273,16 +330,6 @@ impl Doc<Unverified> {
"delegate list cannot be empty",
));
}
if self.default_branch.is_empty() {
return Err(VerificationError::DefaultBranch(
"default branch cannot be empty",
));
}
if self.default_branch.len() > MAX_STRING_LENGTH {
return Err(VerificationError::DefaultBranch(
"default branch cannot exceed 255 bytes",
));
}
if self.threshold > self.delegates.len() {
return Err(VerificationError::Threshold(
self.threshold,
@ -298,7 +345,6 @@ impl Doc<Unverified> {
Ok(Doc {
payload: self.payload,
extensions: self.extensions,
delegates: self.delegates,
threshold: self.threshold,
verified: PhantomData,
@ -500,9 +546,13 @@ mod test {
delegate.public_key().to_human(),
String::from("z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi")
);
assert_eq!(
(*id).to_string(),
"d96f425412c9f8ad5d9a9a05c9831d0728e2338d"
);
assert_eq!(
id.to_human(),
String::from("rad:z2TBtGrJKGsremYAPec6vN4n77Ba7")
String::from("rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji")
);
}
@ -542,14 +592,16 @@ mod test {
// TODO: In some cases we want to get the repo and the project, but don't
// want to have to create a repository object twice. Perhaps there should
// be a way of getting a project from a repo.
let mut proj = storage.get(alice.public_key(), id).unwrap().unwrap();
let mut doc = storage.get(alice.public_key(), id).unwrap().unwrap();
let mut prj = doc.project().unwrap();
let repo = storage.repository(id).unwrap();
// Make a change to the description and sign it.
proj.payload.description += "!";
proj.sign(&alice)
prj.description += "!";
doc.payload.insert(PayloadId::project(), prj.clone().into());
doc.sign(&alice)
.and_then(|(_, sig)| {
proj.update(
doc.update(
alice.public_key(),
"Update description",
&[(alice.public_key(), sig)],
@ -559,11 +611,11 @@ mod test {
.unwrap();
// Add Bob as a delegate, and sign it.
proj.delegate(*bob.public_key());
proj.threshold = 2;
proj.sign(&alice)
doc.delegate(*bob.public_key());
doc.threshold = 2;
doc.sign(&alice)
.and_then(|(_, sig)| {
proj.update(
doc.update(
alice.public_key(),
"Add bob",
&[(alice.public_key(), sig)],
@ -573,11 +625,11 @@ mod test {
.unwrap();
// Add Eve as a delegate, and sign it.
proj.delegate(*eve.public_key());
proj.sign(&alice)
doc.delegate(*eve.public_key());
doc.sign(&alice)
.and_then(|(_, alice_sig)| {
proj.sign(&bob).and_then(|(_, bob_sig)| {
proj.update(
doc.sign(&bob).and_then(|(_, bob_sig)| {
doc.update(
alice.public_key(),
"Add eve",
&[(alice.public_key(), alice_sig), (bob.public_key(), bob_sig)],
@ -588,12 +640,13 @@ mod test {
.unwrap();
// Update description again with signatures by Eve and Bob.
proj.payload.description += "?";
let (current, head) = proj
prj.description += "?";
doc.payload.insert(PayloadId::project(), prj.into());
let (current, head) = doc
.sign(&bob)
.and_then(|(_, bob_sig)| {
proj.sign(&eve).and_then(|(blob_id, eve_sig)| {
proj.update(
doc.sign(&eve).and_then(|(blob_id, eve_sig)| {
doc.update(
alice.public_key(),
"Update description",
&[(bob.public_key(), bob_sig), (eve.public_key(), eve_sig)],
@ -614,10 +667,10 @@ mod test {
assert_eq!(identity.root, id);
assert_eq!(identity.current, current);
assert_eq!(identity.head, head);
assert_eq!(identity.doc, proj);
assert_eq!(identity.doc, doc);
let proj = storage.get(alice.public_key(), id).unwrap().unwrap();
assert_eq!(proj.description, "Acme's repository!?");
let doc = storage.get(alice.public_key(), id).unwrap().unwrap();
assert_eq!(doc.project().unwrap().description, "Acme's repository!?");
}
#[quickcheck]

View File

@ -26,7 +26,7 @@ pub mod prelude {
use super::*;
pub use crypto::{PublicKey, Signer, Verified};
pub use identity::{Did, Doc, Id};
pub use identity::{project::Project, Did, Doc, Id};
pub use node::NodeId;
pub use profile::Profile;
pub use storage::{BranchName, ReadRepository, ReadStorage, WriteRepository, WriteStorage};

View File

@ -8,7 +8,7 @@ use thiserror::Error;
use crate::crypto::{Signer, Verified};
use crate::git;
use crate::identity::project::DocError;
use crate::identity::project::{self, DocError, Project};
use crate::identity::Id;
use crate::node;
use crate::node::NodeId;
@ -60,13 +60,12 @@ pub fn init<G: Signer>(
// TODO: Better error when project id already exists in storage, but remote doesn't.
let pk = signer.public_key();
let delegate = identity::Did::from(*pk);
let doc = identity::Doc::initial(
name.to_owned(),
description.to_owned(),
default_branch.clone(),
delegate,
)
.verified()?;
let proj = Project {
name: name.to_owned(),
description: description.to_owned(),
default_branch: default_branch.clone(),
};
let doc = identity::Doc::initial(proj, delegate).verified()?;
let (id, _, project) = doc.create(pk, "Initialize Radicle\n", storage)?;
let url = git::Url::from(id).with_namespace(*pk);
@ -96,6 +95,8 @@ pub enum ForkError {
Git(#[from] git::Error),
#[error("storage: {0}")]
Storage(#[from] storage::Error),
#[error("payload: {0}")]
Payload(#[from] project::PayloadError),
#[error("project `{0}` was not found in storage")]
NotFound(Id),
#[error("project identity error: {0}")]
@ -123,9 +124,10 @@ pub fn fork_remote<G: Signer, S: storage::WriteStorage>(
// refs/namespaces/<pk>/refs/tags/*
let me = signer.public_key();
let project = storage
let doc = storage
.get(remote, proj)?
.ok_or(ForkError::NotFound(proj))?;
let project = doc.project()?;
let repository = storage.repository(proj)?;
let raw = repository.raw();
@ -247,6 +249,8 @@ pub enum CheckoutError {
Git(#[from] git2::Error),
#[error("storage: {0}")]
Storage(#[from] storage::Error),
#[error("payload: {0}")]
Payload(#[from] project::PayloadError),
#[error("project `{0}` was not found in storage")]
NotFound(Id),
#[error("project error: {0}")]
@ -263,9 +267,10 @@ pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
) -> Result<git2::Repository, CheckoutError> {
// TODO: Decide on whether we can use `clone_local`
// TODO: Look into sharing object databases.
let project = storage
let doc = storage
.get(remote, proj)?
.ok_or(CheckoutError::NotFound(proj))?;
let project = doc.project()?;
let mut opts = git2::RepositoryInitOptions::new();
opts.no_reinit(true).description(&project.description);
@ -375,7 +380,8 @@ mod tests {
)
.unwrap();
let project = storage.get(&public_key, proj).unwrap().unwrap();
let doc = storage.get(&public_key, proj).unwrap().unwrap();
let project = doc.project().unwrap();
let remotes: HashMap<_, _> = storage
.repository(proj)
.unwrap()
@ -402,7 +408,7 @@ mod tests {
assert_eq!(project.name, "acme");
assert_eq!(project.description, "Acme's repo");
assert_eq!(project.default_branch, git::refname!("master"));
assert_eq!(project.delegates.first(), &Did::from(public_key));
assert_eq!(doc.delegates.first(), &Did::from(public_key));
}
#[test]

View File

@ -11,7 +11,7 @@ use radicle_cob::{self as cob, change};
use crate::git;
use crate::identity;
use crate::identity::project::{Identity, IdentityError};
use crate::identity::project::{Identity, IdentityError, Project};
use crate::identity::{Doc, Id};
use crate::storage::refs;
use crate::storage::refs::{Refs, SignedRefs};
@ -41,6 +41,8 @@ pub enum ProjectError {
Storage(#[from] Error),
#[error("identity document error: {0}")]
Doc(#[from] identity::project::DocError),
#[error("payload error: {0}")]
Payload(#[from] identity::project::PayloadError),
#[error("identity verification error: {0}")]
Verify(#[from] identity::project::VerificationError),
#[error("git: {0}")]
@ -96,7 +98,7 @@ impl ReadStorage for Storage {
fn get(&self, remote: &RemoteId, proj: Id) -> Result<Option<Doc<Verified>>, ProjectError> {
// TODO: Don't create a repo here if it doesn't exist?
// Perhaps for checking we could have a `contains` method?
match self.repository(proj)?.project_of(remote) {
match self.repository(proj)?.identity_of(remote) {
Ok(doc) => Ok(Some(doc)),
Err(err) if err.is_not_found() => Ok(None),
@ -271,7 +273,14 @@ impl Repository {
Identity::load(remote, self)
}
pub fn project_of(&self, remote: &RemoteId) -> Result<identity::Doc<Verified>, ProjectError> {
pub fn project_of(&self, remote: &RemoteId) -> Result<Project, ProjectError> {
let doc = self.identity_of(remote)?;
let proj = doc.project()?;
Ok(proj)
}
pub fn identity_of(&self, remote: &RemoteId) -> Result<Doc<Verified>, ProjectError> {
let (doc, _) = identity::Doc::load(remote, self)?;
let verified = doc.verified()?;
@ -279,7 +288,7 @@ impl Repository {
}
/// Return the canonical identity [`git::Oid`] and document.
pub fn project(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError> {
pub fn identity_doc(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError> {
let mut heads = Vec::new();
for remote in self.remote_ids()? {
let remote = remote?;
@ -478,7 +487,7 @@ impl ReadRepository for Repository {
}
fn project_identity(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError> {
Repository::project(self)
Repository::identity_doc(self)
}
fn head(&self) -> Result<(Qualified, Oid), ProjectError> {
@ -494,12 +503,14 @@ impl ReadRepository for Repository {
fn canonical_head(&self) -> Result<(Qualified, Oid), ProjectError> {
// TODO: In the `fork` function for example, we call Repository::project_identity again,
// This should only be necessary once.
let (_, project) = self.project_identity()?;
let (_, doc) = self.project_identity()?;
let doc = doc.verified()?;
let project = doc.project()?;
let branch_ref = Qualified::from(lit::refs_heads(&project.default_branch));
let raw = self.raw();
let mut heads = Vec::new();
for delegate in project.delegates.iter() {
for delegate in doc.delegates.iter() {
let r = self.reference_oid(delegate, &branch_ref)?.into();
heads.push(r);

View File

@ -10,7 +10,7 @@ use qcheck::Arbitrary;
use crate::collections::HashMap;
use crate::git;
use crate::identity::{project::Doc, Did, Id};
use crate::identity::{project::Doc, project::Project, Did, Id};
use crate::storage;
use crate::storage::refs::{Refs, SignedRefs};
use crate::test::storage::MockStorage;
@ -74,18 +74,7 @@ impl Arbitrary for Did {
}
}
impl Arbitrary for Doc<Unverified> {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let name = String::arbitrary(g);
let description = String::arbitrary(g);
let default_branch = git::RefString::try_from(String::arbitrary(g)).unwrap();
let delegate = Did::arbitrary(g);
Self::initial(name, description, default_branch, delegate)
}
}
impl Arbitrary for Doc<Verified> {
impl Arbitrary for Project {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
let name = iter::repeat_with(|| rng.alphanumeric())
@ -99,14 +88,35 @@ impl Arbitrary for Doc<Verified> {
.collect::<String>()
.try_into()
.unwrap();
Project {
name,
description,
default_branch,
}
}
}
impl Arbitrary for Doc<Unverified> {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let proj = Project::arbitrary(g);
let delegate = Did::arbitrary(g);
Self::initial(proj, delegate)
}
}
impl Arbitrary for Doc<Verified> {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
let project = Project::arbitrary(g);
let delegates: NonEmpty<_> = iter::repeat_with(|| Did::arbitrary(g))
.take(rng.usize(1..6))
.collect::<Vec<_>>()
.try_into()
.unwrap();
let threshold = delegates.len() / 2 + 1;
let doc: Doc<Unverified> =
Doc::new(name, description, default_branch, delegates, threshold);
let doc: Doc<Unverified> = Doc::new(project, delegates, threshold);
doc.verified().unwrap()
}