use std::collections::BTreeMap;
use chrono::{DateTime, Utc};
use derive_builder::Builder;
use crate::api::common::{EnableState, SortOrder};
use crate::api::endpoint_prelude::*;
use crate::api::ParamValue;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UserOrderBy {
Id,
Name,
Username,
CreatedAt,
UpdatedAt,
}
#[allow(clippy::derivable_impls)]
impl Default for UserOrderBy {
fn default() -> Self {
UserOrderBy::Id
}
}
impl UserOrderBy {
fn as_str(self) -> &'static str {
match self {
UserOrderBy::Id => "id",
UserOrderBy::Name => "name",
UserOrderBy::Username => "username",
UserOrderBy::CreatedAt => "created_at",
UserOrderBy::UpdatedAt => "updated_at",
}
}
fn use_keyset_pagination(self) -> bool {
matches!(self, Self::Id | Self::Name | Self::Username)
}
}
impl ParamValue<'static> for UserOrderBy {
fn as_value(&self) -> Cow<'static, str> {
self.as_str().into()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Builder)]
pub struct ExternalProvider<'a> {
#[builder(setter(into))]
uid: Cow<'a, str>,
#[builder(setter(into))]
name: Cow<'a, str>,
}
impl<'a> ExternalProvider<'a> {
pub fn builder() -> ExternalProviderBuilder<'a> {
ExternalProviderBuilder::default()
}
pub(crate) fn add_query_params<'b>(&'b self, params: &mut QueryParams<'b>) {
params
.push("extern_uid", &self.uid)
.push("provider", &self.name);
}
pub(crate) fn add_form_params<'b>(&'b self, params: &mut FormParams<'b>) {
params
.push("extern_uid", &self.uid)
.push("provider", &self.name);
}
}
impl<'a> ExternalProviderBuilder<'a> {
#[deprecated(note = "use `uid` instead")]
pub fn id(&mut self, id: u64) -> &mut ExternalProviderBuilder<'a> {
self.uid(id.to_string())
}
}
#[derive(Debug, Builder, Clone)]
#[builder(setter(strip_option))]
pub struct Users<'a> {
#[builder(setter(into), default)]
search: Option<Cow<'a, str>>,
#[builder(setter(into), default)]
username: Option<Cow<'a, str>>,
#[builder(default)]
active: Option<()>,
#[builder(default)]
blocked: Option<()>,
#[builder(default)]
external_provider: Option<ExternalProvider<'a>>,
#[builder(default)]
external: Option<()>,
#[builder(default)]
created_before: Option<DateTime<Utc>>,
#[builder(default)]
created_after: Option<DateTime<Utc>>,
#[builder(setter(name = "_custom_attributes"), default, private)]
custom_attributes: BTreeMap<Cow<'a, str>, Cow<'a, str>>,
#[builder(default)]
with_custom_attributes: Option<bool>,
#[builder(default)]
order_by: Option<UserOrderBy>,
#[builder(default)]
sort: Option<SortOrder>,
#[builder(setter(into), default)]
two_factor: Option<EnableState>,
#[builder(default)]
without_projects: Option<bool>,
#[builder(default)]
without_project_bots: Option<bool>,
#[builder(default)]
exclude_internal: Option<bool>,
#[builder(default)]
exclude_external: Option<bool>,
#[builder(default)]
admins: Option<bool>,
#[builder(default)]
auditors: Option<bool>,
#[builder(default)]
saml_provider_id: Option<u64>,
#[builder(default)]
skip_ldap: Option<bool>,
}
impl<'a> Users<'a> {
pub fn builder() -> UsersBuilder<'a> {
UsersBuilder::default()
}
}
impl<'a> UsersBuilder<'a> {
pub fn custom_attribute<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.custom_attributes
.get_or_insert_with(BTreeMap::new)
.insert(key.into(), value.into());
self
}
pub fn custom_attributes<I, K, V>(&mut self, iter: I) -> &mut Self
where
I: Iterator<Item = (K, V)>,
K: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.custom_attributes
.get_or_insert_with(BTreeMap::new)
.extend(iter.map(|(k, v)| (k.into(), v.into())));
self
}
}
impl<'a> Endpoint for Users<'a> {
fn method(&self) -> Method {
Method::GET
}
fn endpoint(&self) -> Cow<'static, str> {
"users".into()
}
fn parameters(&self) -> QueryParams {
let mut params = QueryParams::default();
params
.push_opt("search", self.search.as_ref())
.push_opt("username", self.username.as_ref())
.push_opt("active", self.active.map(|()| true))
.push_opt("blocked", self.blocked.map(|()| true))
.push_opt("external", self.external.map(|()| true))
.push_opt("created_before", self.created_before)
.push_opt("created_after", self.created_after)
.extend(
self.custom_attributes
.iter()
.map(|(key, value)| (format!("custom_attributes[{}]", key), value)),
)
.push_opt("with_custom_attributes", self.with_custom_attributes)
.push_opt("order_by", self.order_by)
.push_opt("sort", self.sort)
.push_opt("two_factor", self.two_factor)
.push_opt("without_projects", self.without_projects)
.push_opt("without_project_bots", self.without_project_bots)
.push_opt("exclude_internal", self.exclude_internal)
.push_opt("exclude_external", self.exclude_external)
.push_opt("admins", self.admins)
.push_opt("auditors", self.auditors)
.push_opt("saml_provider_id", self.saml_provider_id)
.push_opt("skip_ldap", self.skip_ldap);
if let Some(external_provider) = self.external_provider.as_ref() {
external_provider.add_query_params(&mut params);
}
params
}
}
impl<'a> Pageable for Users<'a> {
fn use_keyset_pagination(&self) -> bool {
self.order_by.unwrap_or_default().use_keyset_pagination()
}
}
#[cfg(test)]
mod tests {
use chrono::{TimeZone, Utc};
use crate::api::common::{EnableState, SortOrder};
use crate::api::users::{ExternalProvider, ExternalProviderBuilderError, UserOrderBy, Users};
use crate::api::{self, Query};
use crate::test::client::{ExpectedUrl, SingleTestClient};
#[test]
fn order_by_default() {
assert_eq!(UserOrderBy::default(), UserOrderBy::Id);
}
#[test]
fn order_by_as_str() {
let items = &[
(UserOrderBy::Id, "id"),
(UserOrderBy::Name, "name"),
(UserOrderBy::Username, "username"),
(UserOrderBy::CreatedAt, "created_at"),
(UserOrderBy::UpdatedAt, "updated_at"),
];
for (i, s) in items {
assert_eq!(i.as_str(), *s);
}
}
#[test]
fn external_provider_uid_and_name_are_necessary() {
let err = ExternalProvider::builder().build().unwrap_err();
crate::test::assert_missing_field!(err, ExternalProviderBuilderError, "uid");
}
#[test]
fn external_provider_uid_is_necessary() {
let err = ExternalProvider::builder()
.name("name")
.build()
.unwrap_err();
crate::test::assert_missing_field!(err, ExternalProviderBuilderError, "uid");
}
#[test]
fn external_provider_name_is_necessary() {
let err = ExternalProvider::builder().uid("1").build().unwrap_err();
crate::test::assert_missing_field!(err, ExternalProviderBuilderError, "name");
}
#[test]
fn external_provider_uid_and_name_are_sufficient() {
ExternalProvider::builder()
.uid("1")
.name("name")
.build()
.unwrap();
}
#[test]
fn defaults_are_sufficient() {
Users::builder().build().unwrap();
}
#[test]
fn endpoint() {
let endpoint = ExpectedUrl::builder().endpoint("users").build().unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_search() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("search", "special/query")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().search("special/query").build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_username() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("username", "user")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().username("user").build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_active() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("active", "true")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().active(()).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_blocked() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("blocked", "true")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().blocked(()).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_external_provider() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("extern_uid", "1"), ("provider", "provider")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder()
.external_provider(
ExternalProvider::builder()
.uid("1")
.name("provider")
.build()
.unwrap(),
)
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_external() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("external", "true")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().external(()).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_created_before() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("created_before", "2020-01-01T00:00:00Z")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder()
.created_before(Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_created_after() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("created_after", "2020-01-01T00:00:00Z")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder()
.created_after(Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_custom_attributes() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[
("custom_attributes[key]", "value"),
("custom_attributes[key2]", "value"),
("custom_attributes[key3]", "value&value"),
])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder()
.custom_attribute("key", "value")
.custom_attributes([("key2", "value"), ("key3", "value&value")].iter().cloned())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_with_custom_attributes() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("with_custom_attributes", "true")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder()
.with_custom_attributes(true)
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_order_by() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("order_by", "id")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().order_by(UserOrderBy::Id).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_sort() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("sort", "desc")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder()
.sort(SortOrder::Descending)
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_two_factor() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("two_factor", "disabled")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder()
.two_factor(EnableState::Disabled)
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_without_projects() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("without_projects", "false")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().without_projects(false).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_without_project_bots() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("without_project_bots", "false")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder()
.without_project_bots(false)
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_exclude_internal() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("exclude_internal", "false")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().exclude_internal(false).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_exclude_external() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("exclude_external", "false")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().exclude_external(false).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_admins() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("admins", "false")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().admins(false).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_auditors() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("auditors", "false")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().auditors(false).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_saml_provider_id() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("saml_provider_id", "2")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().saml_provider_id(2).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn endpoint_skip_ldap() {
let endpoint = ExpectedUrl::builder()
.endpoint("users")
.add_query_params(&[("skip_ldap", "true")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Users::builder().skip_ldap(true).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
}