diff --git a/Cargo.toml b/Cargo.toml index 5227c3b..3dbd07d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,18 +8,16 @@ description = "WIP ATProto SKD" [workspace] members = [ - "lexgen" + "lexgen", + "triphosphate-vocab", ] [dependencies] -chrono = { version = "0.4.26", features = ["serde"] } -cid = "0.10.1" -regex = "1.9.3" +anyhow = "1.0.75" reqwest = { version = "0.11.18", features = ["json"] } serde = { version = "=1.0.171", features = ["derive"] } serde_json = "1.0.104" tokio = { version = "1.29.1", features = ["rt", "macros"] } -winnow = "0.5.10" [dev-dependencies] anyhow = "1.0.75" diff --git a/examples/get_record.rs b/examples/get_record.rs deleted file mode 100644 index f328e4d..0000000 --- a/examples/get_record.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/examples/hello.rs b/examples/hello.rs index 88694bc..f98ee1b 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,8 +1,5 @@ -use triphosphate::{ - lex::{app::bsky::feed::Post, com::atproto::repo::create_record}, - vocab::{AtIdentifier, Datetime, Nsid, StringFormat}, - LexItem, -}; +use triphosphate::lex::app::bsky::feed::Post; +use triphosphate_vocab::{AtIdentifier, Datetime}; #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { @@ -21,21 +18,11 @@ async fn main() -> anyhow::Result<()> { langs: None, reply: None, labels: None, - text: "Hello from Triphosphate! Now contains procedures!".to_string(), + text: "Now no longer hard coding NSID's into createRecord!".to_string(), }; - let resp = create_record( - &client, - &create_record::Args { - collection: Nsid::from_str(Post::URI).unwrap(), - record: serde_json::to_value(&post)?, - repo: AtIdentifier::Did(creds.did), - validate: Some(true), - rkey: None, - swap_commit: None, - }, - ) - .await?; + let my_repo = AtIdentifier::Did(creds.did); + let resp = client.create_record(&post, my_repo).await?; dbg!(resp); diff --git a/examples/shitpost.rs b/examples/shitpost.rs new file mode 100644 index 0000000..930086e --- /dev/null +++ b/examples/shitpost.rs @@ -0,0 +1,22 @@ +use triphosphate::lex::app::bsky::feed::Post; +use triphosphate_vocab::{AtIdentifier, StringFormat}; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> anyhow::Result<()> { + let mut client = triphosphate::client::Client::new()?; + + let handle = std::env::var("ATP_USERNAME").unwrap(); + let password = std::env::var("ATP_PASSWORD").unwrap(); + + client.login(&handle, &password).await?; + + let handle = AtIdentifier::from_str("triphosphate-tests.bsky.social")?; + + let p = client + .get_record::(handle, "3k5fdh2qmie2m".to_owned()) + .await?; + + dbg!(p); + + Ok(()) +} diff --git a/lexgen/Cargo.toml b/lexgen/Cargo.toml index 30eb78e..9829a0f 100644 --- a/lexgen/Cargo.toml +++ b/lexgen/Cargo.toml @@ -15,4 +15,4 @@ quote = { version = "1.0.32", default-features = false } serde = { version = "=1.0.171", features = ["derive"] } serde_json = "1.0.103" syn = { version = "2.0.28", features = ["parsing"] } -triphosphate = { version = "0.0.0", path = ".." } +triphosphate-vocab = { version = "0.1.0", path = "../triphosphate-vocab" } diff --git a/lexgen/src/compiller.rs b/lexgen/src/compiller.rs index a5ffa82..8b2d92e 100644 --- a/lexgen/src/compiller.rs +++ b/lexgen/src/compiller.rs @@ -7,7 +7,7 @@ use std::{ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, ToTokens}; -use triphosphate::vocab::StringFormat; +use triphosphate_vocab::{Nsid, StringFormat}; use crate::lexicon::{self, Array, Token, UserType, XrpcBody, XrpcBodySchema, XrpcParameters}; @@ -88,7 +88,7 @@ impl Compiler { fn lower_record(&self, path: &ItemPath, r: &lexicon::Record) -> proc_macro2::TokenStream { let obj = self.lower_object(path, &r.record, &r.description); - let nsid = triphosphate::vocab::Nsid::from_str(&self.doc.id).unwrap(); + let nsid = Nsid::from_str(&self.doc.id).unwrap(); let name = path.name(); let nsid_repr = nsid.as_str(); @@ -114,7 +114,7 @@ impl Compiler { let fields = o .properties .iter() - .map(|(name, prop)| self.lower_field(name, prop, o)); + .map(|(name, prop)| self.lower_obj_prop(name, prop, o)); quote!( #doc @@ -128,7 +128,7 @@ impl Compiler { fn lower_array(&self, path: &ItemPath, arr: &Array) -> TokenStream { let op = &lexicon::ObjectProperty::Array(arr.clone()); - let (field, desc) = FieldType::from_prop(op, &self.doc.id); + let (field, desc) = FieldType::from_obj_prop(op, &self.doc.id); let name = path.name(); @@ -180,7 +180,7 @@ impl Compiler { // TODO: Error handling. quote! { #docs - pub async fn #name(client: &_lex::_rt::Client, args: &_lex::#params_ty) -> ::reqwest::Result<_lex::#ret_type> { + pub async fn #name(client: &_lex::_rt::Client, args: &_lex::#params_ty) -> _lex::_rt::Result<_lex::#ret_type> { client.do_query(#xrpc_id, args).await } } @@ -204,7 +204,7 @@ impl Compiler { quote! { #docs - pub async fn #name(client: &_lex::_rt::Client, args: &_lex::#input_type) -> ::reqwest::Result<_lex::#output_type> { + pub async fn #name(client: &_lex::_rt::Client, args: &_lex::#input_type) -> _lex::_rt::Result<_lex::#output_type> { client.do_procedure(#xrpc_id, args).await } } @@ -216,11 +216,56 @@ impl Compiler { path: &ItemPath, ) -> Option { if let Some(params) = params { - let object = conv::params_as_object(params.clone()); + // Parameters get serialized to a query string, not json. let params_path = path.extend("Params"); - let obj = self.lower_object(¶ms_path, &object, &object.description); + let mut fields = Vec::new(); + let mut inserters = Vec::new(); + for (lex_name, prop) in ¶ms.properties { + let ty = self.lower_param_prop(lex_name, prop, ¶ms); + + let name = ty.name(); + + let do_access = match &ty.ty { + FieldType::StdString => quote!(#name.clone()), + FieldType::RtType(_) => { + quote!(_lex::_rt::StringFormat::as_str(#name).to_owned()) + } + _ => todo!("{ty:?}"), + }; + + let run_push = if ty.required { + quote!({ + let #name = &self.#name; + r.push((#lex_name, #do_access)); + }) + } else { + quote!(if let Some(#name) = &self.#name { + r.push((#lex_name, #do_access)); + } ) + }; + + inserters.push(run_push); + + fields.push(ty); + } + + let n_required = params.required.len(); + + let obj = quote!( + pub struct Params { + #(#fields),* + } + + impl _lex::_rt::AsParams for Params { + fn as_params(&self) -> Vec<(&'static str, String)> { + let mut r: Vec<(&'static str, String)> = Vec::with_capacity(#n_required); // TODO: Look at optionals. + #(#inserters)* + r + } + } + ); self.insert_item(¶ms_path, obj); @@ -376,8 +421,6 @@ fn doc_comment(desc: &Option) -> proc_macro2::TokenStream { } } -mod conv; - #[cfg(test)] mod tests { diff --git a/lexgen/src/compiller/conv.rs b/lexgen/src/compiller/conv.rs deleted file mode 100644 index e8117da..0000000 --- a/lexgen/src/compiller/conv.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::lexicon::{ - Array, ArrayItem, Object, ObjectProperty, ParameterProperty, Primitive, PrimitveArray, - XrpcParameters, -}; - -pub fn params_as_object(p: XrpcParameters) -> Object { - Object { - required: p.required, - description: p.description, - nullable: Vec::new(), - - properties: p - .properties - .into_iter() - .map(|(name, prop)| (name, param_prop_to_obj_prop(prop))) - .collect(), - - _type: String::new(), - } -} - -fn param_prop_to_obj_prop(p: ParameterProperty) -> ObjectProperty { - match p { - ParameterProperty::Boolean(b) => ObjectProperty::Boolean(b), - ParameterProperty::Integer(i) => ObjectProperty::Integer(i), - ParameterProperty::String(s) => ObjectProperty::String(s), - ParameterProperty::Unknown(u) => ObjectProperty::Unknown(u), - ParameterProperty::Array(a) => ObjectProperty::Array(primitive_array_to_array(a)), - } -} - -fn primitive_array_to_array(p: PrimitveArray) -> Array { - Array { - description: p.description, - min_lenght: p.min_lenght, - max_length: p.max_length, - items: primitive_to_array_item(p.items), - } -} - -fn primitive_to_array_item(p: Primitive) -> ArrayItem { - match p { - Primitive::Boolean(b) => ArrayItem::Boolean(b), - Primitive::Integer(i) => ArrayItem::Integer(i), - Primitive::String(s) => ArrayItem::String(s), - Primitive::Unknown(u) => ArrayItem::Unknown(u), - } -} diff --git a/lexgen/src/compiller/field.rs b/lexgen/src/compiller/field.rs index ce9befe..4a768df 100644 --- a/lexgen/src/compiller/field.rs +++ b/lexgen/src/compiller/field.rs @@ -1,12 +1,12 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, ToTokens}; -use crate::lexicon::{ObjectProperty, StringFormat}; +use crate::lexicon::{Boolean, ObjectProperty, ParameterProperty, StringFormat, XrpcParameters}; use super::{doc_comment, ident, path_for_def, snake, Compiler, ItemPath}; impl Compiler { - pub(super) fn lower_field( + pub(super) fn lower_obj_prop( &self, name: &str, prop: &ObjectProperty, @@ -14,22 +14,44 @@ impl Compiler { ) -> Field { let required = o.required.contains(&name.to_owned()); - let (ty, docs) = FieldType::from_prop(prop, &self.doc.id); + let (ty, docs) = FieldType::from_obj_prop(prop, &self.doc.id); Field { name: name.to_owned(), docs: docs.to_owned(), ty, required, + use_serde: true, + } + } + + pub(super) fn lower_param_prop( + &self, + name: &str, + prop: &ParameterProperty, + ps: &XrpcParameters, + ) -> Field { + let required = ps.required.contains(&name.to_owned()); + + let (ty, docs) = FieldType::from_param_prop(prop); + + Field { + name: name.to_owned(), + docs: docs.to_owned(), + required, + ty, + use_serde: false, } } } +#[derive(Debug)] pub(super) struct Field { - name: String, + pub name: String, docs: Option, - required: bool, - ty: FieldType, + pub required: bool, + pub ty: FieldType, + use_serde: bool, } impl quote::ToTokens for Field { @@ -40,16 +62,20 @@ impl quote::ToTokens for Field { let ty = &self.ty; ( quote!(Option<#ty>), - quote!(#[serde(default, skip_serializing_if = "Option::is_none")]), + if self.use_serde { + quote!(#[serde(default, skip_serializing_if = "Option::is_none")]) + } else { + quote!() + }, ) }; - let (name, serde_name) = field_name(&self.name); + let (name, serde_rename) = field_name(&self.name); let doc = doc_comment(&self.docs); quote!( - #serde_name + #serde_rename #serde_optional #doc pub #name: #ty @@ -76,6 +102,7 @@ fn field_name(name: &str) -> (Ident, TokenStream) { ) } +#[derive(Debug)] pub(super) enum FieldType { Ref(ItemPath), @@ -94,20 +121,21 @@ pub(super) enum FieldType { } impl FieldType { - pub fn from_prop<'a>(prop: &'a ObjectProperty, doc_id: &str) -> (Self, &'a Option) { + pub fn from_obj_prop<'a>(prop: &'a ObjectProperty, doc_id: &str) -> (Self, &'a Option) { match prop { ObjectProperty::Ref(path) => { (FieldType::Ref(type_ref(path, doc_id)), &path.description) } - ObjectProperty::String(s) => (Self::str(s), &s.description), - ObjectProperty::Integer(i) => (Self::int(i), &i.description), + // Shared with param_prop + ObjectProperty::Boolean(b) => Self::bool(b), + ObjectProperty::String(s) => Self::string(s), + ObjectProperty::Integer(i) => Self::integer(i), + + // TODO: Implement. ObjectProperty::Union(u) => (FieldType::Unit, &u.description), ObjectProperty::Array(a) => (FieldType::Unit, &a.description), - // TODO: Handle default, const - ObjectProperty::Boolean(b) => (FieldType::Bool, &b.description), - ObjectProperty::Unknown(u) => (FieldType::Unknown, &u.description), // TODO: Blob details. @@ -117,30 +145,30 @@ impl FieldType { } } - fn str(s: &crate::lexicon::LexString) -> FieldType { - if let Some(format) = &s.format { - return FieldType::RtType(*format); + fn from_param_prop<'a>(prop: &'a ParameterProperty) -> (Self, &'a Option) { + match prop { + ParameterProperty::Boolean(b) => Self::bool(b), + ParameterProperty::Integer(i) => Self::integer(i), + ParameterProperty::String(s) => Self::string(s), + ParameterProperty::Unknown(_) => todo!(), + ParameterProperty::Array(_) => todo!(), } + } - // TODO: Do more here. + fn string(s: &crate::lexicon::LexString) -> (FieldType, &Option) { + let this = if let Some(format) = &s.format { + FieldType::RtType(*format) + } else { + // TODO: Do more here. + FieldType::StdString + }; - FieldType::StdString + (this, &s.description) } - fn int(i: &crate::lexicon::Integer) -> FieldType { - // if i.minimum == Some(0) { - // if i.maximum.is_none() { - // FieldType::U64 // Sensible fallback. - // } else { - // todo!() - // } - // } else { - // todo!("{i:?}"); - // } - + fn integer(i: &crate::lexicon::Integer) -> (Self, &Option) { // Max int size is 64 bits when not stated: https://atproto.com/specs/data-model#data-types. - - match i.minimum { + let this = match i.minimum { Some(0) => match i.maximum { None => FieldType::U64, _ => todo!(), @@ -150,7 +178,14 @@ impl FieldType { _ => todo!(), }, _ => todo!(), - } + }; + + (this, &i.description) + } + + fn bool(b: &Boolean) -> (Self, &Option) { + // TODO: Use default, const. + (Self::Bool, &b.description) } } @@ -189,3 +224,9 @@ impl ToTokens for FieldType { } } } + +impl Field { + pub fn name(&self) -> Ident { + field_name(&self.name).0 + } +} diff --git a/lexgen/src/main.rs b/lexgen/src/main.rs index 85dbabd..87511c1 100644 --- a/lexgen/src/main.rs +++ b/lexgen/src/main.rs @@ -19,10 +19,12 @@ fn main() { include_str!("../lexicons/app/bsky/feed/defs.json"), include_str!("../lexicons/app/bsky/feed/post.json"), include_str!("../lexicons/app/bsky/graph/defs.json"), + include_str!("../lexicons/com/atproto/identity/resolveHandle.json"), include_str!("../lexicons/com/atproto/repo/createRecord.json"), include_str!("../lexicons/com/atproto/repo/getRecord.json"), include_str!("../lexicons/com/atproto/repo/strongRef.json"), include_str!("../lexicons/com/atproto/server/createSession.json"), + include_str!("../lexicons/com/atproto/sync/getHead.json"), ] { let d: LexiconDoc = serde_json::from_str(s).unwrap(); diff --git a/src/client.rs b/src/client.rs index 38ef386..efb30c0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,16 @@ +use anyhow::Context; use reqwest::RequestBuilder; use serde::{de::DeserializeOwned, Serialize}; -use crate::lex::com::atproto::server::create_session; +use triphosphate_vocab::{AtIdentifier, AtUri, Cid}; + +use crate::{ + lex::com::atproto::{ + repo::{create_record, get_record}, + server::create_session, + }, + AsParams, LexRecord, +}; pub struct Client { http: reqwest::Client, @@ -9,7 +18,7 @@ pub struct Client { } impl Client { - pub fn new() -> reqwest::Result { + pub fn new() -> anyhow::Result { let http = reqwest::Client::builder() .user_agent("triphosphate") // TODO: Let users set this. .build()?; @@ -25,7 +34,7 @@ impl Client { &mut self, identifier: &str, password: &str, - ) -> reqwest::Result { + ) -> anyhow::Result { let args = &create_session::Args { identifier: identifier.to_owned(), password: password.to_owned(), @@ -38,21 +47,23 @@ impl Client { Ok(resp) } - pub(crate) async fn do_query( + pub(crate) async fn do_query( &self, id: &'static str, params: &Params, - ) -> reqwest::Result { + ) -> anyhow::Result { // TODO: Error handling. - self.exec(self.http.get(self.xrpc_url(id)).form(params)) - .await + + let url = reqwest::Url::parse_with_params(&self.xrpc_url(id), params.as_params())?; + + self.exec(self.http.get(url)).await } pub(crate) async fn do_procedure( &self, id: &'static str, input: &Input, - ) -> reqwest::Result { + ) -> anyhow::Result { // TODO: Error handling self.exec(self.http.post(self.xrpc_url(id)).json(input)) @@ -64,11 +75,72 @@ impl Client { format!("https://bsky.social/xrpc/{id}") } - async fn exec(&self, mut req: RequestBuilder) -> reqwest::Result { + async fn exec(&self, mut req: RequestBuilder) -> anyhow::Result { if let Some(jwt) = &self.jwt { req = req.bearer_auth(jwt) } - req.send().await?.json().await + let resp = req.send().await?; + + if let Err(e) = resp.error_for_status_ref() { + let body = resp.bytes().await?; + return Err(e).with_context(|| format!("got {body:?}")); + } + + let resp_body = resp.json::().await?; + + Ok(resp_body) + } + + pub async fn create_record( + &self, + record: &R, + repo: AtIdentifier, + ) -> anyhow::Result { + create_record( + self, + &create_record::Args { + collection: R::NSID, + record: serde_json::to_value(record)?, // PERF: Avoid this + repo, + // TODO: Make configurable + rkey: None, + swap_commit: None, + validate: Some(true), + }, + ) + .await } + + pub async fn get_record( + &self, + repo: AtIdentifier, + rkey: String, + ) -> anyhow::Result> { + let resp = get_record( + self, + &get_record::Params { + cid: None, + collection: R::NSID, + repo, + rkey, + }, + ) + .await?; + + let value = serde_json::from_value(resp.value)?; + + Ok(GetRecord { + cid: resp.cid, + uri: resp.uri, + value, + }) + } +} + +#[derive(Debug)] +pub struct GetRecord { + pub cid: Option, + pub value: T, + pub uri: AtUri, } diff --git a/src/lex/com/atproto.rs b/src/lex/com/atproto.rs index e2a37d0..e5d10e4 100644 --- a/src/lex/com/atproto.rs +++ b/src/lex/com/atproto.rs @@ -1,6 +1,8 @@ // Code generated by triphosphate lexgen. DO NOT EDIT. +pub mod identity; pub mod repo; pub mod server; +pub mod sync; #[allow(unused_imports)] use super::super::_lex; diff --git a/src/lex/com/atproto/identity.rs b/src/lex/com/atproto/identity.rs new file mode 100644 index 0000000..e574218 --- /dev/null +++ b/src/lex/com/atproto/identity.rs @@ -0,0 +1,14 @@ +// Code generated by triphosphate lexgen. DO NOT EDIT. +pub mod resolve_handle; + +#[allow(unused_imports)] +use super::super::super::_lex; +///Provides the DID of a repo. +pub async fn resolve_handle( + client: &_lex::_rt::Client, + args: &_lex::com::atproto::identity::resolve_handle::Params, +) -> _lex::_rt::Result<_lex::com::atproto::identity::resolve_handle::Responce> { + client + .do_query("com.atproto.identity.resolveHandle", args) + .await +} diff --git a/src/lex/com/atproto/identity/resolve_handle.rs b/src/lex/com/atproto/identity/resolve_handle.rs new file mode 100644 index 0000000..ce3a7ad --- /dev/null +++ b/src/lex/com/atproto/identity/resolve_handle.rs @@ -0,0 +1,23 @@ +// Code generated by triphosphate lexgen. DO NOT EDIT. + +#[allow(unused_imports)] +use super::super::super::super::_lex; +pub struct Params { + ///The handle to resolve. + pub handle: _lex::_rt::Handle, +} +impl _lex::_rt::AsParams for Params { + fn as_params(&self) -> Vec<(&'static str, String)> { + let mut r: Vec<(&'static str, String)> = Vec::with_capacity(1usize); + { + let handle = &self.handle; + r.push(("handle", _lex::_rt::StringFormat::as_str(handle).to_owned())); + } + r + } +} + +#[derive(::std::fmt::Debug, ::std::clone::Clone, ::serde::Deserialize, ::serde::Serialize)] +pub struct Responce { + pub did: _lex::_rt::Did, +} diff --git a/src/lex/com/atproto/repo.rs b/src/lex/com/atproto/repo.rs index e7708bc..29e1dea 100644 --- a/src/lex/com/atproto/repo.rs +++ b/src/lex/com/atproto/repo.rs @@ -17,7 +17,7 @@ impl _lex::_rt::LexItem for StrongRef { pub async fn create_record( client: &_lex::_rt::Client, args: &_lex::com::atproto::repo::create_record::Args, -) -> ::reqwest::Result<_lex::com::atproto::repo::create_record::Responce> { +) -> _lex::_rt::Result<_lex::com::atproto::repo::create_record::Responce> { client .do_procedure("com.atproto.repo.createRecord", args) .await @@ -27,6 +27,6 @@ pub async fn create_record( pub async fn get_record( client: &_lex::_rt::Client, args: &_lex::com::atproto::repo::get_record::Params, -) -> ::reqwest::Result<_lex::com::atproto::repo::get_record::Responce> { +) -> _lex::_rt::Result<_lex::com::atproto::repo::get_record::Responce> { client.do_query("com.atproto.repo.getRecord", args).await } diff --git a/src/lex/com/atproto/repo/get_record.rs b/src/lex/com/atproto/repo/get_record.rs index 16d456d..2ee7904 100644 --- a/src/lex/com/atproto/repo/get_record.rs +++ b/src/lex/com/atproto/repo/get_record.rs @@ -2,9 +2,7 @@ #[allow(unused_imports)] use super::super::super::super::_lex; -#[derive(::std::fmt::Debug, ::std::clone::Clone, ::serde::Deserialize, ::serde::Serialize)] pub struct Params { - #[serde(default, skip_serializing_if = "Option::is_none")] ///The CID of the version of the record. If not specified, then return the most recent version. pub cid: Option<_lex::_rt::Cid>, ///The NSID of the record collection. @@ -14,6 +12,30 @@ pub struct Params { ///The key of the record. pub rkey: ::std::string::String, } +impl _lex::_rt::AsParams for Params { + fn as_params(&self) -> Vec<(&'static str, String)> { + let mut r: Vec<(&'static str, String)> = Vec::with_capacity(3usize); + if let Some(cid) = &self.cid { + r.push(("cid", _lex::_rt::StringFormat::as_str(cid).to_owned())); + } + { + let collection = &self.collection; + r.push(( + "collection", + _lex::_rt::StringFormat::as_str(collection).to_owned(), + )); + } + { + let repo = &self.repo; + r.push(("repo", _lex::_rt::StringFormat::as_str(repo).to_owned())); + } + { + let rkey = &self.rkey; + r.push(("rkey", rkey.clone())); + } + r + } +} #[derive(::std::fmt::Debug, ::std::clone::Clone, ::serde::Deserialize, ::serde::Serialize)] pub struct Responce { diff --git a/src/lex/com/atproto/server.rs b/src/lex/com/atproto/server.rs index e534e9d..2caad46 100644 --- a/src/lex/com/atproto/server.rs +++ b/src/lex/com/atproto/server.rs @@ -7,7 +7,7 @@ use super::super::super::_lex; pub async fn create_session( client: &_lex::_rt::Client, args: &_lex::com::atproto::server::create_session::Args, -) -> ::reqwest::Result<_lex::com::atproto::server::create_session::Responce> { +) -> _lex::_rt::Result<_lex::com::atproto::server::create_session::Responce> { client .do_procedure("com.atproto.server.createSession", args) .await diff --git a/src/lex/com/atproto/sync.rs b/src/lex/com/atproto/sync.rs new file mode 100644 index 0000000..4a2a004 --- /dev/null +++ b/src/lex/com/atproto/sync.rs @@ -0,0 +1,12 @@ +// Code generated by triphosphate lexgen. DO NOT EDIT. +pub mod get_head; + +#[allow(unused_imports)] +use super::super::super::_lex; +///Gets the current HEAD CID of a repo. +pub async fn get_head( + client: &_lex::_rt::Client, + args: &_lex::com::atproto::sync::get_head::Params, +) -> _lex::_rt::Result<_lex::com::atproto::sync::get_head::Responce> { + client.do_query("com.atproto.sync.getHead", args).await +} diff --git a/src/lex/com/atproto/sync/get_head.rs b/src/lex/com/atproto/sync/get_head.rs new file mode 100644 index 0000000..5eba1d3 --- /dev/null +++ b/src/lex/com/atproto/sync/get_head.rs @@ -0,0 +1,23 @@ +// Code generated by triphosphate lexgen. DO NOT EDIT. + +#[allow(unused_imports)] +use super::super::super::super::_lex; +pub struct Params { + ///The DID of the repo. + pub did: _lex::_rt::Did, +} +impl _lex::_rt::AsParams for Params { + fn as_params(&self) -> Vec<(&'static str, String)> { + let mut r: Vec<(&'static str, String)> = Vec::with_capacity(1usize); + { + let did = &self.did; + r.push(("did", _lex::_rt::StringFormat::as_str(did).to_owned())); + } + r + } +} + +#[derive(::std::fmt::Debug, ::std::clone::Clone, ::serde::Deserialize, ::serde::Serialize)] +pub struct Responce { + pub root: _lex::_rt::Cid, +} diff --git a/src/lib.rs b/src/lib.rs index 676fb0b..ed23833 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,23 @@ pub mod client; pub mod lex; -pub mod vocab; - -mod parsing; pub trait LexItem: serde::Serialize + serde::de::DeserializeOwned { const URI: &'static str; } pub trait LexRecord: LexItem { - const NSID: vocab::Nsid; + const NSID: triphosphate_vocab::Nsid; +} + +pub trait AsParams { + fn as_params(&self) -> Vec<(&'static str, String)>; } pub(crate) mod rt { pub use crate::client::Client; - pub use crate::vocab::*; - pub use crate::{LexItem, LexRecord}; + pub use crate::{AsParams, LexItem, LexRecord}; + pub use triphosphate_vocab::*; + + // TODO: Error handling. + pub use anyhow::Result; } diff --git a/tests/it/main.rs b/tests/it/main.rs index ff0ee8b..143b56e 100644 --- a/tests/it/main.rs +++ b/tests/it/main.rs @@ -1 +1,2 @@ +mod query; mod validation; diff --git a/tests/it/query.rs b/tests/it/query.rs new file mode 100644 index 0000000..ae5c286 --- /dev/null +++ b/tests/it/query.rs @@ -0,0 +1,33 @@ +use cid::Version; +use triphosphate::{ + client::Client, + lex::com::atproto::{identity, sync}, +}; +use triphosphate_vocab::{Did, Handle, StringFormat}; + +#[tokio::test] +async fn get_head() -> anyhow::Result<()> { + let client = triphosphate::client::Client::new()?; + let did = Did::from_str("did:plc:ewvi7nxzyoun6zhxrhs64oiz").unwrap(); + + let cid = sync::get_head(&client, &sync::get_head::Params { did }).await?; + + assert_ne!(cid.root.version(), Version::V0); + + Ok(()) +} + +#[tokio::test] +async fn resolove_atproto_dotcom() -> anyhow::Result<()> { + let handle = Handle::from_str("atproto.com")?; + let client = Client::new()?; + + let resolved_did = Did::from_str("did:plc:ewvi7nxzyoun6zhxrhs64oiz").unwrap(); + + let resp = + identity::resolve_handle(&client, &identity::resolve_handle::Params { handle }).await?; + + assert_eq!(resp.did, resolved_did); + + Ok(()) +} diff --git a/tests/it/validation/post.rs b/tests/it/validation/post.rs index 1ba3edb..6978564 100644 --- a/tests/it/validation/post.rs +++ b/tests/it/validation/post.rs @@ -1,5 +1,6 @@ use chrono::{FixedOffset, TimeZone}; -use triphosphate::{lex::app::bsky::feed::Post, vocab::Datetime}; +use triphosphate::lex::app::bsky::feed::Post; +use triphosphate_vocab::Datetime; use super::check; diff --git a/triphosphate-vocab/Cargo.toml b/triphosphate-vocab/Cargo.toml new file mode 100644 index 0000000..d6ac28a --- /dev/null +++ b/triphosphate-vocab/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "triphosphate-vocab" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4.26", features = ["serde"] } +cid = "0.10.1" +regex = "1.9.3" +serde = { version = "=1.0.171", features = ["derive"] } # TODO: Remove derive +serde_json = "1.0.105" +winnow = "0.5.10" diff --git a/src/vocab/at_identifer.rs b/triphosphate-vocab/src/at_identifer.rs similarity index 100% rename from src/vocab/at_identifer.rs rename to triphosphate-vocab/src/at_identifer.rs diff --git a/src/vocab/at_uri.rs b/triphosphate-vocab/src/at_uri.rs similarity index 100% rename from src/vocab/at_uri.rs rename to triphosphate-vocab/src/at_uri.rs diff --git a/src/vocab/cid.rs b/triphosphate-vocab/src/cid.rs similarity index 77% rename from src/vocab/cid.rs rename to triphosphate-vocab/src/cid.rs index 97295dc..b4549ef 100644 --- a/src/vocab/cid.rs +++ b/triphosphate-vocab/src/cid.rs @@ -20,9 +20,15 @@ impl super::StringFormat for Cid { } } +impl Cid { + pub fn version(&self) -> cid::Version { + self.cid.version() + } +} + #[cfg(test)] mod tests { - use crate::vocab::StringFormat; + use crate::StringFormat; use super::*; @@ -30,9 +36,10 @@ mod tests { #[test] fn valid() { - for s in ["bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a"] { - // let cid = Cid::from_str(s).unwrap(); - // assert_eq!(cid.as_str(), s); + for s in [ + "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", + "bafyreifi5bqq7og5qxedc5xllono4vlpnfvl4pcbskymzcm5kjmbhgobmu", + ] { match Cid::from_str(s) { Ok(cid) => assert_eq!(cid.as_str(), s), Err(e) => panic!("failed to parse {s:?}: {e}"), diff --git a/src/vocab/datetime.rs b/triphosphate-vocab/src/datetime.rs similarity index 100% rename from src/vocab/datetime.rs rename to triphosphate-vocab/src/datetime.rs diff --git a/src/vocab/did.rs b/triphosphate-vocab/src/did.rs similarity index 99% rename from src/vocab/did.rs rename to triphosphate-vocab/src/did.rs index 1cca7f5..c7bad73 100644 --- a/src/vocab/did.rs +++ b/triphosphate-vocab/src/did.rs @@ -4,7 +4,8 @@ use crate::parsing; use super::{ParseError, StringFormat}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] +// TODO: Don't be vis here pub struct Did(pub(super) String); impl StringFormat for Did { diff --git a/src/vocab/handle.rs b/triphosphate-vocab/src/handle.rs similarity index 100% rename from src/vocab/handle.rs rename to triphosphate-vocab/src/handle.rs diff --git a/src/vocab.rs b/triphosphate-vocab/src/lib.rs similarity index 91% rename from src/vocab.rs rename to triphosphate-vocab/src/lib.rs index 5dfdf54..c286b95 100644 --- a/src/vocab.rs +++ b/triphosphate-vocab/src/lib.rs @@ -35,6 +35,7 @@ mod datetime; mod did; mod handle; mod nsid; +mod parsing; pub use self::cid::Cid; pub use at_identifer::AtIdentifier; @@ -48,7 +49,7 @@ macro_rules! serde_impls { ($($name:path)*) => {$( impl ::serde::Serialize for $name { fn serialize(&self, serializer: S) -> Result { - ::serde::Serialize::serialize($crate::vocab::StringFormat::as_str(self), serializer) + ::serde::Serialize::serialize($crate::StringFormat::as_str(self), serializer) } } @@ -67,7 +68,7 @@ macro_rules! serde_impls { } fn visit_str(self, v: &str) -> Result { - $crate::vocab::StringFormat::from_str(v).map_err(E::custom) + $crate::StringFormat::from_str(v).map_err(E::custom) } } diff --git a/src/vocab/nsid.rs b/triphosphate-vocab/src/nsid.rs similarity index 96% rename from src/vocab/nsid.rs rename to triphosphate-vocab/src/nsid.rs index cc58d55..36c11ab 100644 --- a/src/vocab/nsid.rs +++ b/triphosphate-vocab/src/nsid.rs @@ -40,12 +40,14 @@ impl Nsid { } #[doc(hidden)] - pub(crate) const fn __new_unchecked(s: &'static str, last_dot: usize) -> Self { + /// Only to be used by lexgen, where it knows that's it's parsed. + pub const fn __new_unchecked(s: &'static str, last_dot: usize) -> Self { let repr = Cow::Borrowed(s); if s.as_bytes()[last_dot] != b'.' { panic!("invalid last_dot index"); } + // TODO: More asserts here Self { repr, last_dot } } diff --git a/src/parsing.rs b/triphosphate-vocab/src/parsing.rs similarity index 100% rename from src/parsing.rs rename to triphosphate-vocab/src/parsing.rs diff --git a/src/parsing/at_uri.rs b/triphosphate-vocab/src/parsing/at_uri.rs similarity index 100% rename from src/parsing/at_uri.rs rename to triphosphate-vocab/src/parsing/at_uri.rs diff --git a/src/parsing/regexes.rs b/triphosphate-vocab/src/parsing/regexes.rs similarity index 100% rename from src/parsing/regexes.rs rename to triphosphate-vocab/src/parsing/regexes.rs