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: libs #166

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
26 changes: 26 additions & 0 deletions .github/workflows/libs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Libs

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

env:
CARGO_TERM_COLOR: always

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: |
cargo build -p atrium-libs --verbose
cargo build -p atrium-libs --verbose --features common-web
cargo build -p atrium-libs --verbose --features identity
cargo build -p atrium-libs --verbose --all-features
- name: Run tests
run: |
cargo test -p atrium-libs --lib
cargo test -p atrium-libs --lib --all-features
33 changes: 27 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
members = [
"atrium-api",
"atrium-cli",
"atrium-libs",
"atrium-xrpc",
"atrium-xrpc-client",
]
Expand All @@ -27,7 +28,7 @@ atrium-xrpc-client = { version = "0.5.2", path = "atrium-xrpc-client" }

# async in traits
# Can be removed once MSRV is at least 1.75.0.
async-trait = "0.1.68"
async-trait = "0.1.80"

# DAG-CBOR codec
ipld-core = { version = "0.4.0", default-features = false, features = ["std"] }
Expand All @@ -37,15 +38,17 @@ serde_ipld_dagcbor = { version = "0.6.0", default-features = false, features = [
chrono = "0.4"
langtag = "0.3"
regex = "1"
serde = "1.0.160"
serde = "1.0.199"
serde_bytes = "0.11.9"
serde_json = "1.0.96"
serde_html_form = "0.2.6"
urlencoding = "2.1.3"

# Networking
futures = { version = "0.3.30", default-features = false, features = ["alloc"] }
http = "1.1.0"
tokio = { version = "1.36", default-features = false }
url = "2.5.0"

# HTTP client integrations
isahc = "1.7.2"
Expand Down
32 changes: 32 additions & 0 deletions atrium-libs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "atrium-libs"
version = "0.1.0"
authors = ["sugyan <[email protected]>"]
edition.workspace = true
rust-version.workspace = true
description = "A collection of libraries for AT Protocol"
readme = "README.md"
repository.workspace = true
license.workspace = true
keywords.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-trait = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }
thiserror = { workspace = true, optional = true }
url = { workspace = true, optional = true }
urlencoding = { workspace = true, optional = true }

[dev-dependencies]
mockito = { workspace = true }
reqwest = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["macros"] }

[features]
default = []
common-web = ["serde/derive"]
identity = ["common-web", "async-trait", "serde_json", "thiserror", "url", "urlencoding"]
1 change: 1 addition & 0 deletions atrium-libs/src/common_web.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod did_doc;
84 changes: 84 additions & 0 deletions atrium-libs/src/common_web/did_doc.rs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These types already exist in atrium_api::did_doc (and look almost identical, other than missing the context field on DidDocument). Is the plan to move these types out of atrium-api, or is this just a temporary duplication as part of figuring out where atrium-libs sits in the crate dependency tree?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a look!
Yes, currently I am thinking of removing atrium_api::did_doc and referencing common_web::did_doc from the api by adding these new libs.

Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! Definitions for DID document types.
//! https://atproto.com/specs/did#did-documents

/// A DID document, containing information associated with the DID
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DidDocument {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "@context")]
pub context: Option<Vec<String>>,
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub also_known_as: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub verification_method: Option<Vec<VerificationMethod>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service: Option<Vec<Service>>,
}

/// The public signing key for the account
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct VerificationMethod {
pub id: String,
pub r#type: String,
pub controller: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub public_key_multibase: Option<String>,
}

/// The PDS service network location for the account
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Service {
pub id: String,
pub r#type: String,
pub service_endpoint: String,
}

#[cfg(test)]
mod tests {
use super::*;

const DID_DOC_JSON: &str = r##"{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1","https://w3id.org/security/suites/secp256k1-2019/v1"],"id":"did:plc:4ee6oesrsbtmuln4gqsqf6fp","alsoKnownAs":["at://sugyan.com"],"verificationMethod":[{"id":"did:plc:4ee6oesrsbtmuln4gqsqf6fp#atproto","type":"Multikey","controller":"did:plc:4ee6oesrsbtmuln4gqsqf6fp","publicKeyMultibase":"zQ3shnw8ChQwGUE6gMghuvn5g7Q9YVej1MUJENqMsLmxZwRSz"}],"service":[{"id":"#atproto_pds","type":"AtprotoPersonalDataServer","serviceEndpoint":"https://puffball.us-east.host.bsky.network"}]}"##;

fn did_doc_example() -> DidDocument {
DidDocument {
context: Some(vec![
String::from("https://www.w3.org/ns/did/v1"),
String::from("https://w3id.org/security/multikey/v1"),
String::from("https://w3id.org/security/suites/secp256k1-2019/v1"),
]),
id: String::from("did:plc:4ee6oesrsbtmuln4gqsqf6fp"),
also_known_as: Some(vec![String::from("at://sugyan.com")]),
verification_method: Some(vec![VerificationMethod {
id: String::from("did:plc:4ee6oesrsbtmuln4gqsqf6fp#atproto"),
r#type: String::from("Multikey"),
controller: String::from("did:plc:4ee6oesrsbtmuln4gqsqf6fp"),
public_key_multibase: Some(String::from(
"zQ3shnw8ChQwGUE6gMghuvn5g7Q9YVej1MUJENqMsLmxZwRSz",
)),
}]),
service: Some(vec![Service {
id: String::from("#atproto_pds"),
r#type: String::from("AtprotoPersonalDataServer"),
service_endpoint: String::from("https://puffball.us-east.host.bsky.network"),
}]),
}
}

#[test]
fn serialize_did_doc() {
let result =
serde_json::to_string(&did_doc_example()).expect("serialization should succeed");
assert_eq!(result, DID_DOC_JSON);
}

#[test]
fn deserialize_did_doc() {
let result = serde_json::from_str::<DidDocument>(DID_DOC_JSON)
.expect("deserialization should succeed");
assert_eq!(result, did_doc_example());
}
}
2 changes: 2 additions & 0 deletions atrium-libs/src/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
//! A library for decentralized identities in [atproto](https://atproto.com) using DIDs and handles
pub mod did;
43 changes: 43 additions & 0 deletions atrium-libs/src/identity/did.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::common_web::did_doc::DidDocument;
pub mod did_resolver;
mod error;
mod plc_resolver;
mod web_resolver;

use self::error::{Error, Result};
use async_trait::async_trait;

#[async_trait]
pub trait Fetch {
async fn fetch(
url: &str,
timeout: Option<u64>,
) -> std::result::Result<Option<Vec<u8>>, Box<dyn std::error::Error + Send + Sync + 'static>>;
}

#[async_trait]
pub trait Resolve {
async fn resolve_no_check(&self, did: &str) -> Result<Option<Vec<u8>>>;
async fn resolve_no_cache(&self, did: &str) -> Result<Option<DidDocument>> {
if let Some(got) = self.resolve_no_check(did).await? {
Ok(serde_json::from_slice(&got)?)
} else {
Ok(None)
}
}
async fn resolve(&self, did: &str, force_refresh: bool) -> Result<Option<DidDocument>> {
// TODO: from cache
if let Some(got) = self.resolve_no_cache(did).await? {
// TODO: store in cache
Ok(Some(got))
} else {
// TODO: clear cache
Ok(None)
}
}
async fn ensure_resolve(&self, did: &str, force_refresh: bool) -> Result<DidDocument> {
self.resolve(did, force_refresh)
.await?
.ok_or_else(|| Error::DidNotFound(did.to_string()))
}
}
Loading
Loading