diff --git a/libft-api-derive/src/lib.rs b/libft-api-derive/src/lib.rs index 47387e2..1477f45 100644 --- a/libft-api-derive/src/lib.rs +++ b/libft-api-derive/src/lib.rs @@ -1,107 +1,431 @@ -//! Procedural macros for the `libft-api` crate. -//! -//! This crate provides procedural macros that are used to reduce boilerplate code -//! in the main `libft-api` crate. The macros are implemented as derive macros -//! that automatically generate trait implementations for data structures. -//! -//! # Available Macros -//! -//! * `HasVector` - Derives the `HasVec` trait for structs that contain exactly one `Vec` field - extern crate proc_macro; use proc_macro::TokenStream; -use quote::quote; +use quote::{format_ident, quote, ToTokens}; use syn::{ - parse_macro_input, spanned::Spanned, Data, DeriveInput, Error, Fields, GenericArgument, - PathArguments, Type, + parse_macro_input, spanned::Spanned, AngleBracketedGenericArguments, Attribute, Data, + DataStruct, DeriveInput, Fields, GenericArgument, Ident, Path, PathArguments, + Result as SynResult, }; -/// Derives the `HasVec` trait for structs that contain exactly one `Vec` field. -/// -/// This macro automatically implements the `HasVec` trait for structs that have -/// exactly one named field of type `Vec`. The generated implementation provides -/// methods to access and take ownership of the vector field. -/// -/// # Requirements -/// * The struct must have exactly one field of type `Vec` -/// * The struct must have named fields (not tuple or unit structs) -/// * The field type must be exactly `Vec`, not an alias or reference -#[proc_macro_derive(HasVector)] -pub fn has_vec_derive(input: TokenStream) -> TokenStream { +/// #[derive(HasItems)] +/// #[has_items( +/// crate = "libft_api", // optional: prefix for HasItems/Values/Entries (e.g., "libft_api") +/// field = "users", // optional when only one supported field exists +/// modes = "both" // "values" | "entries" | "both" ; default: +/// // Vec -> "values" +/// // HashMap -> "both" +/// )] +#[proc_macro_derive(HasItems, attributes(has_items))] +pub fn derive_has_items(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - expand_has_vec(ast) + expand_has_items(ast) .unwrap_or_else(|e| e.to_compile_error()) .into() } -fn expand_has_vec(ast: DeriveInput) -> Result { - let struct_name = &ast.ident; - - let field = match &ast.data { - Data::Struct(s) => match &s.fields { - Fields::Named(named) => { - named.named.iter().find(|f| is_vec(&f.ty)).ok_or_else(|| { - Error::new( - s.fields.span(), - "HasVector requires exactly one named field of type Vec", - ) - })? - } - _ => { - return Err(Error::new( - s.fields.span(), - "HasVector currently supports only named-field structs", - )) - } - }, +#[derive(Default)] +struct DeriveOpts { + /// crate path prefix for HasItems, Values, Entries. e.g., libft_api + crate_path: Option, + /// name of the field to use (if multiple candidates exist) + field_name: Option, + /// modes: values | entries | both + modes: Option, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Modes { + Values, + Entries, + Both, +} + +impl Modes { + fn parse(s: &str) -> Option { + match s.to_ascii_lowercase().as_str() { + "values" => Some(Modes::Values), + "entries" => Some(Modes::Entries), + "both" => Some(Modes::Both), + _ => None, + } + } +} + +fn expand_has_items(input: DeriveInput) -> SynResult { + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let struct_ident = &input.ident; + + let opts = parse_attrs(&input.attrs)?; + + let ds = match input.data { + Data::Struct(ds) => ds, _ => { - return Err(Error::new( - ast.span(), - "HasVector can only be derived for structs", + return Err(syn::Error::new( + input.span(), + "HasItems derive only supports structs", )) } }; - let field_ident = field - .ident - .clone() - .ok_or_else(|| Error::new(field.span(), "expected a named field"))?; - - let inner_ty = extract_vec_inner_ty(&field.ty).ok_or_else(|| { - Error::new( - field.ty.span(), - "field must be exactly Vec (no aliases or refs)", - ) - })?; - - Ok(quote! { - impl HasVec<#inner_ty> for #struct_name { - fn get_vec(&self) -> &Vec<#inner_ty> { &self.#field_ident } - fn take_vec(self) -> Vec<#inner_ty> { self.#field_ident } + let (field_ident, kind) = pick_field(&ds, opts.field_name.as_ref())?; + + // Default modes by kind if not specified + let modes = opts.modes.unwrap_or(match kind { + FieldKind::Vec { .. } => Modes::Values, + FieldKind::HashMap { .. } => Modes::Both, + }); + + let crate_prefix = opts + .crate_path + .map(|p| p.to_token_stream()) + .unwrap_or_else(|| quote!()); + + // Paths for traits/markers (with optional crate prefix) + let path_has_items = if crate_prefix.is_empty() { + quote!(HasItems) + } else { + quote!(#crate_prefix::HasItems) + }; + let path_values = if crate_prefix.is_empty() { + quote!(Values) + } else { + quote!(#crate_prefix::Values) + }; + let path_entries = if crate_prefix.is_empty() { + quote!(Entries) + } else { + quote!(#crate_prefix::Entries) + }; + + // Generate impl(s) + let mut impls = Vec::new(); + + let context = HasItemContext::new( + struct_ident, + impl_generics, + ty_generics, + &field_ident, + &path_has_items, + &path_values, + &path_entries, + ) + .with_where_clause(where_clause); + match kind { + FieldKind::Vec { t } => { + // Vec supports only Values + if matches!(modes, Modes::Entries) { + return Err(syn::Error::new( + field_ident.span(), + "modes = \"entries\" is not supported for Vec. Use modes = \"values\".", + )); + } + impls.push(gen_impl_vec_values(&context, &t)); + } + FieldKind::HashMap { k, v } => { + match modes { + Modes::Values | Modes::Both => impls.push(gen_impl_map_values(&context, &k, &v)), + _ => {} + } + match modes { + Modes::Entries | Modes::Both => impls.push(gen_impl_map_entries(&context, &k, &v)), + _ => {} + } } - }) + } + + Ok(quote! { #(#impls)* }) } -fn is_vec(ty: &Type) -> bool { - matches!( - ty, - Type::Path(tp) - if tp.path.segments.last().map(|s| s.ident == "Vec").unwrap_or(false) - ) +fn parse_attrs(attrs: &[Attribute]) -> SynResult { + let mut opts = DeriveOpts::default(); + + for attr in attrs { + if !attr.path().is_ident("has_items") { + continue; + } + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("crate") { + let value = meta.value()?; + let s: syn::LitStr = value.parse()?; + opts.crate_path = Some(syn::parse_str(&s.value())?); + } + if meta.path.is_ident("field") { + let value = meta.value()?; + let s: syn::LitStr = value.parse()?; + opts.field_name = Some(format_ident!("{}", s.value())); + } + if meta.path.is_ident("modes") { + let value = meta.value()?; + let s: syn::LitStr = value.parse()?; + opts.modes = Modes::parse(&s.value()); + } + Ok(()) + })?; + } + + Ok(opts) +} + +enum FieldKind { + Vec { + t: Box, + }, + HashMap { + k: Box, + v: Box, + }, } -fn extract_vec_inner_ty(ty: &Type) -> Option { - if let Type::Path(tp) = ty { - if let Some(seg) = tp.path.segments.last() { - if seg.ident == "Vec" { - if let PathArguments::AngleBracketed(args) = &seg.arguments { - if let Some(GenericArgument::Type(inner)) = args.args.first() { - return Some(inner.clone()); +fn pick_field(ds: &DataStruct, wanted: Option<&Ident>) -> SynResult<(Ident, FieldKind)> { + let named = match &ds.fields { + Fields::Named(n) => &n.named, + Fields::Unnamed(_) | Fields::Unit => { + return Err(syn::Error::new( + ds.struct_token.span, + "tuple/unit structs are not supported; use a named-field struct", + )) + } + }; + + let mut candidates: Vec<(Ident, FieldKind)> = Vec::new(); + + for f in named { + let ident = f.ident.clone().unwrap(); + if let Some(w) = wanted { + if &ident != w { + continue; + } + } + if let syn::Type::Path(tp) = &f.ty { + if let Some(seg) = tp.path.segments.last() { + if seg.ident == "Vec" { + if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { + args, + .. + }) = &seg.arguments + { + if let Some(GenericArgument::Type(t)) = args.first() { + candidates.push(( + ident, + FieldKind::Vec { + t: Box::new(t.clone()), + }, + )); + continue; + } + } + } else if seg.ident == "HashMap" { + if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { + args, + .. + }) = &seg.arguments + { + if args.len() == 2 { + if let ( + Some(GenericArgument::Type(k)), + Some(GenericArgument::Type(v)), + ) = (args.first(), args.iter().nth(1)) + { + candidates.push(( + ident, + FieldKind::HashMap { + k: Box::new(k.clone()), + v: Box::new(v.clone()), + }, + )); + continue; + } + } } } } } } - None + + if let Some(w) = wanted { + candidates + .into_iter() + .next() + .ok_or_else(|| syn::Error::new(w.span(), "specified field not found or unsupported")) + } else { + match candidates.len() { + 0 => Err(syn::Error::new( + ds.struct_token.span, + "no supported field found (need Vec or HashMap)", + )), + 1 => Ok(candidates.into_iter().next().unwrap()), + _ => Err(syn::Error::new( + ds.struct_token.span, + "multiple candidate fields. Use #[has_items(field = \"...\")]", + )), + } + } +} + +struct HasItemContext<'a> { + struct_ident: &'a Ident, + impl_generics: syn::ImplGenerics<'a>, + ty_generics: syn::TypeGenerics<'a>, + where_clause: Option<&'a syn::WhereClause>, + field_ident: &'a Ident, + path_has_items: &'a proc_macro2::TokenStream, + path_values: &'a proc_macro2::TokenStream, + path_entries: &'a proc_macro2::TokenStream, +} + +impl<'a> HasItemContext<'a> { + fn new( + struct_ident: &'a Ident, + impl_generics: syn::ImplGenerics<'a>, + ty_generics: syn::TypeGenerics<'a>, + field_ident: &'a Ident, + path_has_items: &'a proc_macro2::TokenStream, + path_values: &'a proc_macro2::TokenStream, + path_entries: &'a proc_macro2::TokenStream, + ) -> Self { + Self { + struct_ident, + impl_generics, + ty_generics, + where_clause: None, + field_ident, + path_has_items, + path_values, + path_entries, + } + } + + fn with_where_clause(mut self, where_clause: Option<&'a syn::WhereClause>) -> Self { + self.where_clause = where_clause; + self + } +} + +// type HasItemContext<'a> = { +// struct_ident: &'a Ident, +// impl_generics: syn::ImplGenerics<'a>, +// ty_generics: syn::TypeGenerics<'a>, +// where_clause: Option<&'a syn::WhereClause>, +// field_ident: &'a Ident, +// t: &'a syn::Type, +// path_has_items: &'a proc_macro2::TokenStream, +// path_values: &'a proc_macro2::TokenStream, +// }; +// + +fn gen_impl_vec_values( + context: &HasItemContext, + t: &syn::Type, // struct_ident: &Ident, + // impl_generics: syn::ImplGenerics<'_>, + // ty_generics: syn::TypeGenerics<'_>, + // where_clause: Option<&syn::WhereClause>, + // field_ident: &Ident, + // t: &syn::Type, + // path_has_items: &proc_macro2::TokenStream, + // path_values: &proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + let HasItemContext { + struct_ident, + impl_generics, + ty_generics, + where_clause, + field_ident, + path_has_items, + path_values, + path_entries: _, + } = context; + quote! { + impl #impl_generics #path_has_items<#path_values> for #struct_ident #ty_generics #where_clause { + type OwnedItem = #t; + type BorrowedItem<'a> = &'a #t; + type IntoItems = ::std::vec::Vec<#t>; + type IterItems<'a> = ::std::slice::Iter<'a, #t>; + + fn into_items(self) -> Self::IntoItems { + self.#field_ident + } + fn iter_items(&self) -> Self::IterItems<'_> { + self.#field_ident.iter() + } + } + } +} + +fn gen_impl_map_values( + context: &HasItemContext, + // struct_ident: &Ident, + // impl_generics: syn::ImplGenerics<'_>, + // ty_generics: syn::TypeGenerics<'_>, + // where_clause: Option<&syn::WhereClause>, + // field_ident: &Ident, + // path_has_items: &proc_macro2::TokenStream, + // path_values: &proc_macro2::TokenStream, + k: &syn::Type, + v: &syn::Type, +) -> proc_macro2::TokenStream { + let HasItemContext { + struct_ident, + impl_generics, + ty_generics, + where_clause, + field_ident, + path_has_items, + path_values, + path_entries: _, + } = context; + quote! { + impl #impl_generics #path_has_items<#path_values> for #struct_ident #ty_generics #where_clause { + type OwnedItem = #v; + type BorrowedItem<'a> = &'a #v; + type IntoItems = ::std::collections::hash_map::IntoValues<#k, #v>; + type IterItems<'a> = ::std::collections::hash_map::Values<'a, #k, #v>; + + fn into_items(self) -> Self::IntoItems { + self.#field_ident.into_values() + } + fn iter_items(&self) -> Self::IterItems<'_> { + self.#field_ident.values() + } + } + } +} + +fn gen_impl_map_entries( + // struct_ident: &Ident, + // impl_generics: syn::ImplGenerics<'_>, + // ty_generics: syn::TypeGenerics<'_>, + // where_clause: Option<&syn::WhereClause>, + // field_ident: &Ident, + // path_has_items: &proc_macro2::TokenStream, + // path_entries: &proc_macro2::TokenStream, + context: &HasItemContext, + k: &syn::Type, + v: &syn::Type, +) -> proc_macro2::TokenStream { + let HasItemContext { + struct_ident, + impl_generics, + ty_generics, + where_clause, + field_ident, + path_has_items, + path_values: _, + path_entries, + } = context; + quote! { + impl #impl_generics #path_has_items<#path_entries> for #struct_ident #ty_generics #where_clause { + type OwnedItem = (#k, #v); + type BorrowedItem<'a> = (&'a #k, &'a #v); + type IntoItems = ::std::collections::hash_map::IntoIter<#k, #v>; + type IterItems<'a> = ::std::collections::hash_map::Iter<'a, #k, #v>; + + fn into_items(self) -> Self::IntoItems { + self.#field_ident.into_iter() + } + fn iter_items(&self) -> Self::IterItems<'_> { + self.#field_ident.iter() + } + } + } } diff --git a/libft-api/Cargo.toml b/libft-api/Cargo.toml index 6541411..b89081f 100644 --- a/libft-api/Cargo.toml +++ b/libft-api/Cargo.toml @@ -14,6 +14,10 @@ path="src/lib.rs" name = "piscine_users" path = "bin/piscine_users.rs" +[[bin]] +name = "locations" +path = "bin/locations.rs" + [[example]] name = "scroll" diff --git a/libft-api/bin/locations.rs b/libft-api/bin/locations.rs new file mode 100644 index 0000000..8dfffe9 --- /dev/null +++ b/libft-api/bin/locations.rs @@ -0,0 +1,50 @@ +use std::{io::Write, sync::Arc}; + +use futures::FutureExt; +use libft_api::{info::ft_campus_id::GYEONGSAN, prelude::*}; +use tokio::task::JoinSet; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + let client = Arc::new(FtClient::new(FtClientReqwestConnector::new())); + + let req: ReqFn<_> = |session, page| { + async move { + session + .users( + FtApiUsersRequest::new() + .with_page(page) + .with_per_page(100) + .with_filter(vec![FtFilterOption::new( + FtFilterField::PrimaryCampusId, + vec![GYEONGSAN.to_string()], + )]) + .with_range(vec![FtRangeOption::new( + FtRangeField::CreatedAt, + vec!["2025-09-01".to_owned(), "2025-10-20".to_owned()], + )]), + ) + .await + } + .boxed() + }; + + let mut handles = JoinSet::new(); + for i in 1..=8 { + let client = Arc::clone(&client); + handles.spawn(async move { scroller(&client, 8, i, req).await }); + } + + let mut result = Vec::new(); + while let Some(res) = handles.join_next().await { + match res { + Ok(v) => result.extend(v), + Err(e) => tracing::error!("task failed: {e}"), + } + } + + let mut file = std::fs::File::create("pisciner.json").unwrap(); + file.write_all(serde_json::to_string_pretty(&result).unwrap().as_bytes()) + .unwrap(); +} diff --git a/libft-api/src/api.rs b/libft-api/src/api.rs index 8415baf..e1783b8 100644 --- a/libft-api/src/api.rs +++ b/libft-api/src/api.rs @@ -48,14 +48,24 @@ pub mod user; pub mod prelude; -/// Convenience abstraction for wrapper types that contain a `Vec` under a single field. -/// -/// This trait simplifies access to vector fields in API response types. -/// -pub trait HasVec { - /// Get a reference to the contained vector. - fn get_vec(&self) -> &Vec; +// 모드 마커 +pub trait CollectMode {} +pub enum Values {} +impl CollectMode for Values {} +pub enum Entries {} +impl CollectMode for Entries {} - /// Take ownership of the contained vector. - fn take_vec(self) -> Vec; +pub trait HasItems { + type OwnedItem; + type BorrowedItem<'a> + where + Self: 'a; + + type IntoItems: IntoIterator; + type IterItems<'a>: Iterator> + where + Self: 'a; + + fn into_items(self) -> Self::IntoItems; + fn iter_items(&self) -> Self::IterItems<'_>; } diff --git a/libft-api/src/api/campus/campus_id.rs b/libft-api/src/api/campus/campus_id.rs index 4cfebd8..af1ec6a 100644 --- a/libft-api/src/api/campus/campus_id.rs +++ b/libft-api/src/api/campus/campus_id.rs @@ -1,6 +1,5 @@ use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; use rsb_derive::Builder; use serde::{Deserialize, Serialize}; @@ -14,7 +13,7 @@ pub struct FtApiCampusIdRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiCampusIdResponse { pub campus: Vec, diff --git a/libft-api/src/api/campus/campus_id_journals.rs b/libft-api/src/api/campus/campus_id_journals.rs index 42a62b2..541e468 100644 --- a/libft-api/src/api/campus/campus_id_journals.rs +++ b/libft-api/src/api/campus/campus_id_journals.rs @@ -5,8 +5,6 @@ use tracing::debug; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; - #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiCampusIdJournalsRequest { pub user_id: Option, @@ -20,7 +18,7 @@ pub struct FtApiCampusIdJournalsRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiCampusIdJournalsResponse { pub journals: Vec, diff --git a/libft-api/src/api/campus/campus_id_locations.rs b/libft-api/src/api/campus/campus_id_locations.rs index 49b9fac..041714e 100644 --- a/libft-api/src/api/campus/campus_id_locations.rs +++ b/libft-api/src/api/campus/campus_id_locations.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; use rsb_derive::Builder; use serde::{Deserialize, Serialize}; use tracing::debug; @@ -16,7 +16,7 @@ pub struct FtApiCampusIdLocationsRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiCampusIdLocationsResponse { pub location: Vec, diff --git a/libft-api/src/api/campus/campus_id_users.rs b/libft-api/src/api/campus/campus_id_users.rs index 5e09e77..ce953b6 100644 --- a/libft-api/src/api/campus/campus_id_users.rs +++ b/libft-api/src/api/campus/campus_id_users.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; use rsb_derive::Builder; use serde::{Deserialize, Serialize}; @@ -15,7 +15,7 @@ pub struct FtApiCampusIdUsersRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiCampusIdUsersResponse { pub users: Vec, diff --git a/libft-api/src/api/campus/campus_users.rs b/libft-api/src/api/campus/campus_users.rs index 48d12ec..bc3a86b 100644 --- a/libft-api/src/api/campus/campus_users.rs +++ b/libft-api/src/api/campus/campus_users.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiCampusUsersRequest { @@ -15,7 +15,7 @@ pub struct FtApiCampusUsersRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiCampusUsersResponse { pub campus_users: Vec, diff --git a/libft-api/src/api/cursus/cursus_id_projects.rs b/libft-api/src/api/cursus/cursus_id_projects.rs index 66ef61c..1b92b77 100644 --- a/libft-api/src/api/cursus/cursus_id_projects.rs +++ b/libft-api/src/api/cursus/cursus_id_projects.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiCursusIdProjectsRequest { @@ -16,7 +16,7 @@ pub struct FtApiCursusIdProjectsRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiCursusIdProjectsResponse { pub projects: Vec, diff --git a/libft-api/src/api/exam/exams.rs b/libft-api/src/api/exam/exams.rs index ef16178..d6663db 100644 --- a/libft-api/src/api/exam/exams.rs +++ b/libft-api/src/api/exam/exams.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiExamsRequest { @@ -24,7 +24,7 @@ pub struct FtApiExamsUsersPostBody { pub user_id: FtUserId, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiExamsResponse { pub exams: Vec, diff --git a/libft-api/src/api/group/groups.rs b/libft-api/src/api/group/groups.rs index ec6f4be..8fe4d7b 100644 --- a/libft-api/src/api/group/groups.rs +++ b/libft-api/src/api/group/groups.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiGroupsRequest { @@ -30,7 +30,7 @@ pub struct FtApiGroupsUsersPostResponse { pub group: FtGroup, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiGroupsResponse { pub groups: Vec, diff --git a/libft-api/src/api/prelude.rs b/libft-api/src/api/prelude.rs index d799b92..9f5a37b 100644 --- a/libft-api/src/api/prelude.rs +++ b/libft-api/src/api/prelude.rs @@ -41,4 +41,7 @@ pub use super::project_user::*; pub use super::scale_team::*; pub use super::user::*; -pub use super::HasVec; +pub use super::Entries; +pub use super::HasItems; +pub use super::Values; +pub use libft_api_derive::HasItems; diff --git a/libft-api/src/api/project/project_data.rs b/libft-api/src/api/project/project_data.rs index 3f07dd0..fa7b73d 100644 --- a/libft-api/src/api/project/project_data.rs +++ b/libft-api/src/api/project/project_data.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiProjectDataRequest { @@ -16,7 +16,7 @@ pub struct FtApiProjectDataRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiProjectDataResponse { pub project_data: Vec, diff --git a/libft-api/src/api/project/projects.rs b/libft-api/src/api/project/projects.rs index cf4bc3b..1e8b372 100644 --- a/libft-api/src/api/project/projects.rs +++ b/libft-api/src/api/project/projects.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiProjectRequest { @@ -16,7 +16,7 @@ pub struct FtApiProjectRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiProjectResponse { pub projects: Vec, diff --git a/libft-api/src/api/project/projects_id_teams.rs b/libft-api/src/api/project/projects_id_teams.rs index 8f0aba5..3d846e9 100644 --- a/libft-api/src/api/project/projects_id_teams.rs +++ b/libft-api/src/api/project/projects_id_teams.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; use rsb_derive::Builder; use serde::{Deserialize, Serialize}; @@ -15,7 +15,7 @@ pub struct FtApiProjectsIdTeamsRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiProjectsIdTeamsResponse { pub teams: Vec, diff --git a/libft-api/src/api/project_session/project_sessions_id_scale_teams.rs b/libft-api/src/api/project_session/project_sessions_id_scale_teams.rs index 93ab9a7..df6640a 100644 --- a/libft-api/src/api/project_session/project_sessions_id_scale_teams.rs +++ b/libft-api/src/api/project_session/project_sessions_id_scale_teams.rs @@ -1,10 +1,10 @@ use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; use rsb_derive::Builder; use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiProjectSessionsScaleTeamsResponse { pub scale_teams: Vec, diff --git a/libft-api/src/api/project_session/project_sessions_id_teams.rs b/libft-api/src/api/project_session/project_sessions_id_teams.rs index eef46e0..21af8c2 100644 --- a/libft-api/src/api/project_session/project_sessions_id_teams.rs +++ b/libft-api/src/api/project_session/project_sessions_id_teams.rs @@ -3,9 +3,9 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiProjectSessionsTeamsResponse { pub teams: Vec, diff --git a/libft-api/src/api/project_user/projects_users.rs b/libft-api/src/api/project_user/projects_users.rs index 46fbfe2..91de0a2 100644 --- a/libft-api/src/api/project_user/projects_users.rs +++ b/libft-api/src/api/project_user/projects_users.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; use rsb_derive::Builder; use serde::{Deserialize, Serialize}; @@ -31,7 +31,7 @@ pub struct FtApiProjectsUsersRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiProjectsUsersResponse { pub projects_users: Vec, diff --git a/libft-api/src/api/scale_team/scale_teams.rs b/libft-api/src/api/scale_team/scale_teams.rs index bedf355..78f9e61 100644 --- a/libft-api/src/api/scale_team/scale_teams.rs +++ b/libft-api/src/api/scale_team/scale_teams.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; use rsb_derive::Builder; use serde::{Deserialize, Serialize}; @@ -14,7 +14,7 @@ pub struct FtApiScaleTeamsRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiScaleTeamsResponse { pub scale_teams: Vec, @@ -31,7 +31,7 @@ pub struct FtApiScaleTeamsMultipleCreateBody { pub team_id: FtTeamId, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiScaleTeamsMultipleCreateResponse { pub scale_teams: Vec, diff --git a/libft-api/src/api/user.rs b/libft-api/src/api/user.rs index 679d91b..832e78f 100644 --- a/libft-api/src/api/user.rs +++ b/libft-api/src/api/user.rs @@ -40,7 +40,7 @@ //! //! // Get a user's location data //! let location_response = session.users_id_locations(FtApiUsersIdLocationsRequest::new(FtUserId::new(12345))).await?; -//! println!("Found {} location records", location_response.get_vec().len()); +//! println!("Found {} location records", location_response.iter_items().len()); //! # Ok(()) //! # } //! # tokio::runtime::Runtime::new().unwrap().block_on(run()).unwrap(); diff --git a/libft-api/src/api/user/users.rs b/libft-api/src/api/user/users.rs index ae6323a..36ca1fe 100644 --- a/libft-api/src/api/user/users.rs +++ b/libft-api/src/api/user/users.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiUsersPostRequest { @@ -38,7 +38,7 @@ pub struct FtApiUserPostsResponse { pub user: FtUser, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiUsersResponse { pub users: Vec, diff --git a/libft-api/src/api/user/users_id_correction_point_historics.rs b/libft-api/src/api/user/users_id_correction_point_historics.rs index 06ee2d4..fdfe63c 100644 --- a/libft-api/src/api/user/users_id_correction_point_historics.rs +++ b/libft-api/src/api/user/users_id_correction_point_historics.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiUsersIdCorrectionPointHistoricsRequest { @@ -15,7 +15,7 @@ pub struct FtApiUsersIdCorrectionPointHistoricsRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiUsersIdCorrectionPointHistoricsResponse { pub historics: Vec, diff --git a/libft-api/src/api/user/users_id_cursus_users.rs b/libft-api/src/api/user/users_id_cursus_users.rs index b603c47..7b71434 100644 --- a/libft-api/src/api/user/users_id_cursus_users.rs +++ b/libft-api/src/api/user/users_id_cursus_users.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiUsersIdCursusUsersRequest { @@ -28,7 +28,7 @@ pub struct FtApiCursusUsersBody { pub has_coalition: bool, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiUsersIdCursusUsersResponse { pub cursus_user: Vec, diff --git a/libft-api/src/api/user/users_id_locations.rs b/libft-api/src/api/user/users_id_locations.rs index d231005..f89a638 100644 --- a/libft-api/src/api/user/users_id_locations.rs +++ b/libft-api/src/api/user/users_id_locations.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiUsersIdLocationsRequest { @@ -15,7 +15,7 @@ pub struct FtApiUsersIdLocationsRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiUsersIdLocationsResponse { pub locations: Vec, diff --git a/libft-api/src/api/user/users_id_projects_users.rs b/libft-api/src/api/user/users_id_projects_users.rs index 2263d95..47f378a 100644 --- a/libft-api/src/api/user/users_id_projects_users.rs +++ b/libft-api/src/api/user/users_id_projects_users.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; use rsb_derive::Builder; use serde::{Deserialize, Serialize}; use tracing::info; @@ -18,7 +18,7 @@ pub struct FtApiUsersIdProjectsUsersRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiUsersIdProjectsUsersResponse { pub projects_users: Vec, diff --git a/libft-api/src/api/user/users_id_teams.rs b/libft-api/src/api/user/users_id_teams.rs index 86ae40d..a3f4f8d 100644 --- a/libft-api/src/api/user/users_id_teams.rs +++ b/libft-api/src/api/user/users_id_teams.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; use crate::to_param; -use libft_api_derive::HasVector; +use libft_api_derive::HasItems; #[derive(Debug, Serialize, Deserialize, Builder)] pub struct FtApiUsersIdTeamsRequest { @@ -18,7 +18,7 @@ pub struct FtApiUsersIdTeamsRequest { pub per_page: Option, } -#[derive(Debug, Serialize, Deserialize, Builder, HasVector)] +#[derive(Debug, Serialize, Deserialize, Builder, HasItems)] #[serde(transparent)] pub struct FtApiUsersIdTeamsResponse { pub teams: Vec, diff --git a/libft-api/src/common/paginator.rs b/libft-api/src/common/paginator.rs index 0607d7e..853583e 100644 --- a/libft-api/src/common/paginator.rs +++ b/libft-api/src/common/paginator.rs @@ -1,6 +1,6 @@ use std::{ops::ControlFlow, sync::Arc, time::Duration}; -use crate::prelude::*; +use crate::{api::CollectMode, prelude::*}; use futures::future::BoxFuture; use tokio::time::sleep; @@ -10,14 +10,15 @@ pub type ReqFn = for<'a> fn( usize, ) -> BoxFuture<'a, ClientResult>; -pub async fn scroller<'a, T, RS, RQ>( +pub async fn scroller<'a, T, M, RS, RQ>( client: &'a FtClient, thread_num: usize, initial_page: usize, request_builder: RQ, ) -> Vec where - RS: for<'de> serde::de::Deserialize<'de> + HasVec, + RS: for<'de> serde::de::Deserialize<'de> + HasItems, + M: CollectMode, RQ: Fn( Arc>, usize, @@ -43,12 +44,12 @@ where match res { Ok(res) => { if *client.meta.total_page.lock().unwrap() as usize <= *page - || res.get_vec().is_empty() + || res.iter_items().next().is_none() { return ControlFlow::Break(()); } - result.extend(res.take_vec()); + result.extend(res.into_items()); *page += thread_num; } Err(FtClientError::RateLimitError(_)) => {