-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
199 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
# Common utilities for Rust | ||
# Bomboni: Common Utilities Library for Rust |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,6 @@ | |
|
||
#[cfg(feature = "macros")] | ||
pub mod macros; | ||
|
||
#[cfg(feature = "request")] | ||
pub mod request; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
//! # Common macros for Rust. | ||
//! # Common macros. | ||
//! | ||
|
||
//! A collection of common macros for Rust. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
//! # Utilities for working with API requests. | ||
|
||
pub mod resource; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
//! # Tools for working with API resources. | ||
|
||
#[cfg(feature = "macros")] | ||
pub use bomboni_derive::parse_resource_name; | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn parse_names() { | ||
let f = parse_resource_name!([ | ||
"users" => u32, | ||
"projects" => u64, | ||
"revisions" => Option<String>, | ||
]); | ||
|
||
let (user_id, project_id, revision_id) = f("users/3/projects/5/revisions/1337").unwrap(); | ||
assert_eq!(user_id, 3); | ||
assert_eq!(project_id, 5); | ||
assert_eq!(revision_id, Some("1337".to_string())); | ||
|
||
let (user_id, project_id, revision_id) = f("users/3/projects/5").unwrap(); | ||
assert_eq!(user_id, 3); | ||
assert_eq!(project_id, 5); | ||
assert!(revision_id.is_none()); | ||
|
||
assert!(parse_resource_name!([ | ||
"a" => u32, | ||
"b" => u32, | ||
])("a/1/b/1/c/1") | ||
.is_none()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
[package] | ||
name = "bomboni_derive" | ||
version = "0.1.2" | ||
authors = ["Tin Rabzelj <tin@flinect.com>"] | ||
description = "Macros for Bomboni library" | ||
repository = "https://github.com/tinrab/bomboni" | ||
homepage = "https://github.com/tinrab/bomboni" | ||
license-file = "../LICENSE" | ||
readme = "../README.md" | ||
edition = "2021" | ||
|
||
[lib] | ||
name = "bomboni_derive" | ||
path = "src/lib.rs" | ||
proc-macro = true | ||
|
||
[dependencies] | ||
proc-macro2 = { version = "1.0.69", features = ["proc-macro"] } | ||
syn = "2.0.39" | ||
quote = "1.0.33" | ||
darling = "0.20.3" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
//! # A procedural macro crate for the Bomboni library. | ||
|
||
use proc_macro::TokenStream; | ||
use proc_macro2::{Literal, Span}; | ||
use quote::quote; | ||
use syn::parse::{Parse, ParseStream, Result}; | ||
use syn::parse_macro_input; | ||
use syn::punctuated::Punctuated; | ||
use syn::{token, Token, Type}; | ||
|
||
use crate::utility::is_option_type; | ||
|
||
mod utility; | ||
|
||
#[derive(Debug)] | ||
struct Resource { | ||
_bracket_token: token::Bracket, | ||
segments: Punctuated<Segment, Token![,]>, | ||
} | ||
|
||
#[derive(Debug)] | ||
struct Segment { | ||
span: Span, | ||
name: Literal, | ||
_arrow_token: Token![=>], | ||
ty: Type, | ||
} | ||
|
||
/// A procedural macro that generates a function that parses a resource name into a tuple of typed segments. | ||
/// Resource name format is documented in Google's AIP [1]. | ||
/// Ending segments can also be optional [2]. | ||
/// | ||
/// [1]: https://google.aip.dev/122 | ||
/// [2]: https://google.aip.dev/162 | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// # use bomboni_derive::parse_resource_name; | ||
/// let name = "users/42/projects/1337"; | ||
/// let parsed = parse_resource_name!([ | ||
/// "users" => u32, | ||
/// "projects" => u64, | ||
/// ])(name); | ||
/// assert_eq!(parsed, Some((42, 1337))); | ||
/// ``` | ||
#[proc_macro] | ||
pub fn parse_resource_name(input: TokenStream) -> TokenStream { | ||
let resource = parse_macro_input!(input as Resource); | ||
|
||
let mut parse_segments = quote!(); | ||
let mut had_optional = false; | ||
for segment in resource.segments.iter() { | ||
let name = &segment.name; | ||
let ty = &segment.ty; | ||
if is_option_type(ty) { | ||
had_optional = true; | ||
parse_segments.extend(quote! {{ | ||
if segments_iter.peek() == Some(&#name) { | ||
segments_iter.next()?; | ||
segments_iter.next().map(|e| e.parse().ok()).flatten() | ||
} else { | ||
None | ||
} | ||
}}); | ||
} else { | ||
if had_optional { | ||
return syn::Error::new(segment.span, "only ending segments can be optional") | ||
.to_compile_error() | ||
.into(); | ||
} | ||
parse_segments.extend(quote! {{ | ||
if segments_iter.next()? != #name { | ||
return None; | ||
} | ||
segments_iter.next()?.parse::<#ty>().ok()? | ||
}}); | ||
} | ||
parse_segments.extend(quote! {,}); | ||
} | ||
|
||
quote! { | ||
|name: &str| { | ||
let segments = name.trim().split('/').collect::<Vec<_>>(); | ||
let mut segments_iter = segments.into_iter().peekable(); | ||
let result = (#parse_segments); | ||
// No extra segments allowed. | ||
if segments_iter.next().is_some() { | ||
return None; | ||
} | ||
Some(result) | ||
} | ||
} | ||
.into() | ||
} | ||
|
||
impl Parse for Resource { | ||
fn parse(input: ParseStream) -> Result<Self> { | ||
let content; | ||
Ok(Resource { | ||
_bracket_token: syn::bracketed!(content in input), | ||
segments: content.parse_terminated(Segment::parse, Token![,])?, | ||
}) | ||
} | ||
} | ||
|
||
impl Parse for Segment { | ||
fn parse(input: ParseStream) -> Result<Self> { | ||
Ok(Segment { | ||
span: input.span(), | ||
name: input.parse()?, | ||
_arrow_token: input.parse()?, | ||
ty: input.parse()?, | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
use syn::Type; | ||
|
||
pub(crate) fn is_option_type(ty: &Type) -> bool { | ||
if let Type::Path(type_path) = ty { | ||
if let Some(segment) = type_path.path.segments.first() { | ||
if segment.ident == "Option" { | ||
return true; | ||
} | ||
} | ||
} | ||
false | ||
} |