Skip to content

Commit

Permalink
Generate structs corresponding to collections
Browse files Browse the repository at this point in the history
This makes operating over collections of records (such as in the
repository format) easier, binding the collection NSID and record type
together.
  • Loading branch information
str4d committed Mar 1, 2024
1 parent b03494d commit d5844a0
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 15 deletions.
17 changes: 17 additions & 0 deletions atrium-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- `atrium_api::types::Collection` trait, which binds together a record type and its NSID.
- Collection structs for the current record types:
- `atrium_api::app::bsky::actor::Profile`
- `atrium_api::app::bsky::feed`:
- `Generator`
- `Like`
- `Post`
- `Repost`
- `Threadgate`
- `atrium_api::app::bsky::graph`:
- `Block`
- `Follow`
- `List`
- `Listblock`
- `Listitem`

## [0.18.0](https://github.com/sugyan/atrium/compare/atrium-api-v0.17.2...atrium-api-v0.18.0) - 2024-02-29

### Added
Expand Down
6 changes: 6 additions & 0 deletions atrium-api/src/app/bsky/actor.rs

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

30 changes: 30 additions & 0 deletions atrium-api/src/app/bsky/feed.rs

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

30 changes: 30 additions & 0 deletions atrium-api/src/app/bsky/graph.rs

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

42 changes: 41 additions & 1 deletion atrium-api/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Definitions for AT Protocol's data models.
//! <https://atproto.com/specs/data-model>
use std::{cell::OnceCell, ops::Deref, str::FromStr};
use std::{cell::OnceCell, fmt, ops::Deref, str::FromStr};

use regex::Regex;

Expand All @@ -20,6 +20,46 @@ pub use integer::*;

pub mod string;

/// Trait for a collection of records that can be stored in a repository.
///
/// The records all have the same Lexicon schema.
pub trait Collection: fmt::Debug {
/// The NSID for the Lexicon that defines the schema of records in this collection.
const NSID: &'static str;

/// This collection's record type.
type Record: fmt::Debug + serde::de::DeserializeOwned + serde::Serialize;

/// Returns the [`Nsid`] for the Lexicon that defines the schema of records in this
/// collection.
///
/// This is a convenience method that parses [`Self::NSID`].
///
/// # Panics
///
/// Panics if [`Self::NSID`] is not a valid NSID.
///
/// [`Nsid`]: string::Nsid
fn nsid() -> string::Nsid {
Self::NSID
.parse()
.expect("Self::NSID should be a valid NSID")
}

/// Returns the repo path for a record in this collection with the given record key.
///
/// Per the [Repo Data Structure v3] specification:
/// > Repo paths currently have a fixed structure of `<collection>/<record-key>`. This
/// > means a valid, normalized [`Nsid`], followed by a `/`, followed by a valid
/// > [`RecordKey`].
///
/// [Repo Data Structure v3]: https://atproto.com/specs/repository#repo-data-structure-v3
/// [`Nsid`]: string::Nsid
fn repo_path(rkey: &RecordKey) -> String {
format!("{}/{}", Self::NSID, rkey.as_str())
}
}

/// A record key (`rkey`) used to name and reference an individual record within the same
/// collection of an atproto repository.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
Expand Down
51 changes: 38 additions & 13 deletions lexicon/atrium-codegen/src/generator.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::fs::find_dirs;
use crate::schema::find_ref_unions;
use crate::token_stream::{client, modules, ref_unions, refs_enum, user_type};
use crate::token_stream::{client, collection, modules, ref_unions, refs_enum, user_type};
use atrium_lex::lexicon::LexUserType;
use atrium_lex::LexiconDoc;
use heck::ToSnakeCase;
Expand Down Expand Up @@ -133,7 +133,10 @@ pub(crate) fn generate_client(
Ok(path)
}

pub(crate) fn generate_modules(outdir: &Path) -> Result<Vec<PathBuf>, Box<dyn Error>> {
pub(crate) fn generate_modules(
outdir: &Path,
schemas: &[LexiconDoc],
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
let mut paths = find_dirs(outdir)?;
paths.reverse();
paths.retain(|p| {
Expand Down Expand Up @@ -163,23 +166,45 @@ pub(crate) fn generate_modules(outdir: &Path) -> Result<Vec<PathBuf>, Box<dyn Er
.sorted()
.collect_vec();
let modules = modules(&names)?;
let documentation = if path.as_ref() == outdir {
quote! {
#![doc = include_str!("../README.md")]
pub use atrium_xrpc as xrpc;
}
let (documentation, collections) = if path.as_ref() == outdir {
(
quote! {
#![doc = include_str!("../README.md")]
pub use atrium_xrpc as xrpc;
},
vec![],
)
} else if let Ok(relative) = path.as_ref().strip_prefix(outdir) {
let doc = format!(
"Definitions for the `{}` namespace.",
relative.to_string_lossy().replace('/', ".")
);
quote!(#![doc = #doc])
let ns = relative.to_string_lossy().replace('/', ".");
let doc = format!("Definitions for the `{}` namespace.", ns);

let collections = names
.iter()
.filter_map(|name| {
let nsid = format!("{}.{}", ns, name);
schemas
.iter()
.find(|schema| {
schema
.defs
.get("main")
.map(|def| {
schema.id == nsid && matches!(def, LexUserType::Record(_))
})
.unwrap_or(false)
})
.map(|_| collection(name, &nsid))
})
.collect_vec();

(quote!(#![doc = #doc]), collections)
} else {
quote!()
(quote!(), vec![])
};
let content = quote! {
#documentation
#modules
#(#collections)*
};
write_to_file(File::create(filepath)?, content)?;
}
Expand Down
2 changes: 1 addition & 1 deletion lexicon/atrium-codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub fn genapi(
}
results.push(generate_records(&outdir, &schemas)?);
results.push(generate_client(&outdir, &schemas)?);
results.extend(generate_modules(&outdir)?);
results.extend(generate_modules(&outdir, &schemas)?);
Ok(results)
}

Expand Down
13 changes: 13 additions & 0 deletions lexicon/atrium-codegen/src/token_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ pub fn ref_unions(schema_id: &str, ref_unions: &[(String, LexRefUnion)]) -> Resu
Ok(quote!(#(#enums)*))
}

pub fn collection(name: &str, nsid: &str) -> TokenStream {
let module_name = format_ident!("{name}");
let collection_name = format_ident!("{}", name.to_pascal_case());
quote! {
#[derive(Debug)]
pub struct #collection_name;
impl crate::types::Collection for #collection_name {
const NSID: &'static str = #nsid;
type Record = #module_name::Record;
}
}
}

fn lex_record(record: &LexRecord) -> Result<TokenStream> {
let LexRecordRecord::Object(object) = &record.record;
lex_object(object, "Record")
Expand Down

0 comments on commit d5844a0

Please sign in to comment.