use std::borrow::Cow;
use std::fmt;
use std::iter;
use std::ops;
use itertools::Itertools;
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
use crate::api::ParamValue;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AccessLevel {
Anonymous,
Minimal,
Guest,
Reporter,
Developer,
Maintainer,
Owner,
Admin,
}
impl AccessLevel {
pub fn as_str(self) -> &'static str {
match self {
AccessLevel::Admin => "admin",
AccessLevel::Owner => "owner",
AccessLevel::Maintainer => "maintainer",
AccessLevel::Developer => "developer",
AccessLevel::Reporter => "reporter",
AccessLevel::Guest => "guest",
AccessLevel::Minimal => "minimal",
AccessLevel::Anonymous => "anonymous",
}
}
pub fn as_u64(self) -> u64 {
match self {
AccessLevel::Admin => 60,
AccessLevel::Owner => 50,
AccessLevel::Maintainer => 40,
AccessLevel::Developer => 30,
AccessLevel::Reporter => 20,
AccessLevel::Guest => 10,
AccessLevel::Minimal => 5,
AccessLevel::Anonymous => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SortOrder {
Ascending,
Descending,
}
impl Default for SortOrder {
fn default() -> Self {
SortOrder::Descending
}
}
impl SortOrder {
pub fn as_str(self) -> &'static str {
match self {
SortOrder::Ascending => "asc",
SortOrder::Descending => "desc",
}
}
}
impl ParamValue<'static> for SortOrder {
fn as_value(&self) -> Cow<'static, str> {
self.as_str().into()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EnableState {
Enabled,
Disabled,
}
impl EnableState {
pub fn as_str(self) -> &'static str {
match self {
EnableState::Enabled => "enabled",
EnableState::Disabled => "disabled",
}
}
}
impl From<bool> for EnableState {
fn from(b: bool) -> Self {
if b {
EnableState::Enabled
} else {
EnableState::Disabled
}
}
}
impl ParamValue<'static> for EnableState {
fn as_value(&self) -> Cow<'static, str> {
self.as_str().into()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NameOrId<'a> {
Name(Cow<'a, str>),
Id(u64),
}
const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &CONTROLS
.add(b' ')
.add(b'"')
.add(b'#')
.add(b'<')
.add(b'>')
.add(b'`')
.add(b'?')
.add(b'{')
.add(b'}')
.add(b'%')
.add(b'/');
pub fn path_escaped(input: &'_ str) -> impl fmt::Display + '_ {
utf8_percent_encode(input, PATH_SEGMENT_ENCODE_SET)
}
impl<'a> fmt::Display for NameOrId<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
NameOrId::Name(name) => write!(f, "{}", path_escaped(name)),
NameOrId::Id(id) => write!(f, "{}", id),
}
}
}
impl<'a> From<u64> for NameOrId<'a> {
fn from(id: u64) -> Self {
NameOrId::Id(id)
}
}
impl<'a> From<&'a str> for NameOrId<'a> {
fn from(name: &'a str) -> Self {
NameOrId::Name(name.into())
}
}
impl<'a> From<String> for NameOrId<'a> {
fn from(name: String) -> Self {
NameOrId::Name(name.into())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VisibilityLevel {
Public,
Internal,
Private,
}
impl VisibilityLevel {
pub fn as_str(self) -> &'static str {
match self {
VisibilityLevel::Public => "public",
VisibilityLevel::Internal => "internal",
VisibilityLevel::Private => "private",
}
}
}
impl ParamValue<'static> for VisibilityLevel {
fn as_value(&self) -> Cow<'static, str> {
self.as_str().into()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum YesNo {
Yes,
No,
}
impl YesNo {
pub fn as_str(self) -> &'static str {
match self {
YesNo::Yes => "yes",
YesNo::No => "no",
}
}
}
impl From<bool> for YesNo {
fn from(b: bool) -> Self {
if b {
YesNo::Yes
} else {
YesNo::No
}
}
}
impl ParamValue<'static> for YesNo {
fn as_value(&self) -> Cow<'static, str> {
self.as_str().into()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ProtectedAccessLevel {
Developer,
Maintainer,
Admin,
NoAccess,
}
impl Default for ProtectedAccessLevel {
fn default() -> Self {
ProtectedAccessLevel::Maintainer
}
}
impl ProtectedAccessLevel {
fn as_str(self) -> &'static str {
match self {
ProtectedAccessLevel::Developer => "30",
ProtectedAccessLevel::Maintainer => "40",
ProtectedAccessLevel::Admin => "60",
ProtectedAccessLevel::NoAccess => "0",
}
}
}
impl ParamValue<'static> for ProtectedAccessLevel {
fn as_value(&self) -> Cow<'static, str> {
self.as_str().into()
}
}
#[derive(Debug, Clone, Default)]
pub struct CommaSeparatedList<T> {
data: Vec<T>,
}
impl<T> CommaSeparatedList<T> {
pub fn new() -> Self {
Self {
data: Vec::new(),
}
}
}
impl<T> From<Vec<T>> for CommaSeparatedList<T> {
fn from(data: Vec<T>) -> Self {
Self {
data,
}
}
}
impl<T> iter::FromIterator<T> for CommaSeparatedList<T> {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
Self {
data: iter.into_iter().collect(),
}
}
}
impl<T> ops::Deref for CommaSeparatedList<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T> ops::DerefMut for CommaSeparatedList<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
impl<T> fmt::Display for CommaSeparatedList<T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.data.iter().format(","))
}
}
impl<'a, T> ParamValue<'a> for CommaSeparatedList<T>
where
T: ParamValue<'a>,
{
fn as_value(&self) -> Cow<'a, str> {
format!("{}", self.data.iter().map(|d| d.as_value()).format(",")).into()
}
}
impl<'a, 'b, T> ParamValue<'a> for &'b CommaSeparatedList<T>
where
T: ParamValue<'a>,
{
fn as_value(&self) -> Cow<'a, str> {
format!("{}", self.data.iter().map(|d| d.as_value()).format(",")).into()
}
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use std::cmp;
use std::iter;
use crate::api::common::{
AccessLevel, CommaSeparatedList, EnableState, NameOrId, ProtectedAccessLevel, SortOrder,
VisibilityLevel, YesNo,
};
use crate::api::params::ParamValue;
#[test]
fn access_level_as_str() {
let items = &[
(AccessLevel::Anonymous, "anonymous", 0),
(AccessLevel::Minimal, "minimal", 5),
(AccessLevel::Guest, "guest", 10),
(AccessLevel::Reporter, "reporter", 20),
(AccessLevel::Developer, "developer", 30),
(AccessLevel::Maintainer, "maintainer", 40),
(AccessLevel::Owner, "owner", 50),
(AccessLevel::Admin, "admin", 60),
];
for (i, s, u) in items {
assert_eq!(i.as_str(), *s);
assert_eq!(i.as_u64(), *u);
}
}
#[test]
fn access_level_ordering() {
let items = &[
AccessLevel::Anonymous,
AccessLevel::Guest,
AccessLevel::Reporter,
AccessLevel::Developer,
AccessLevel::Maintainer,
AccessLevel::Owner,
AccessLevel::Admin,
];
let mut last = None;
for item in items {
if let Some(prev) = last {
assert!(prev < item);
}
last = Some(item);
}
}
#[test]
fn sort_order_default() {
assert_eq!(SortOrder::default(), SortOrder::Descending);
}
#[test]
fn sort_order_as_str() {
let items = &[
(SortOrder::Ascending, "asc"),
(SortOrder::Descending, "desc"),
];
for (i, s) in items {
assert_eq!(i.as_str(), *s);
}
}
#[test]
fn enable_state_as_str() {
let items = &[
(EnableState::Enabled, "enabled"),
(EnableState::Disabled, "disabled"),
];
for (i, s) in items {
assert_eq!(i.as_str(), *s);
}
}
#[test]
fn enable_state_from_bool() {
let items = &[(EnableState::Enabled, true), (EnableState::Disabled, false)];
for (i, s) in items {
assert_eq!(*i, (*s).into());
}
}
#[test]
fn name_or_id_as_str() {
let items: &[(NameOrId, _)] = &[
("user".into(), "user"),
("special/name".into(), "special%2Fname"),
(
"special/name?string".to_string().into(),
"special%2Fname%3Fstring",
),
(1.into(), "1"),
];
for (i, s) in items {
assert_eq!(i.to_string(), *s);
}
}
#[test]
fn visibility_level_as_str() {
let items = &[
(VisibilityLevel::Public, "public"),
(VisibilityLevel::Internal, "internal"),
(VisibilityLevel::Private, "private"),
];
for (i, s) in items {
assert_eq!(i.as_str(), *s);
}
}
#[test]
fn yes_no_as_str() {
let items = &[(YesNo::Yes, "yes"), (YesNo::No, "no")];
for (i, s) in items {
assert_eq!(i.as_str(), *s);
}
}
#[test]
fn yes_no_from_bool() {
let items = &[(YesNo::Yes, true), (YesNo::No, false)];
for (i, s) in items {
assert_eq!(*i, (*s).into());
}
}
#[test]
fn protected_access_level_default() {
assert_eq!(
ProtectedAccessLevel::default(),
ProtectedAccessLevel::Maintainer,
);
}
#[test]
fn protected_access_level_ord() {
let items = &[
ProtectedAccessLevel::Developer,
ProtectedAccessLevel::Maintainer,
ProtectedAccessLevel::Admin,
ProtectedAccessLevel::NoAccess,
];
for i in items {
#[allow(clippy::eq_op)]
{
assert_eq!(*i, *i);
}
assert_eq!(i.cmp(i), cmp::Ordering::Equal);
let mut expect = cmp::Ordering::Greater;
for j in items {
let is_same = i == j;
if is_same {
expect = cmp::Ordering::Equal;
}
assert_eq!(i.cmp(j), expect);
if is_same {
expect = cmp::Ordering::Less;
}
}
let mut expect = cmp::Ordering::Less;
for j in items.iter().rev() {
let is_same = i == j;
if is_same {
expect = cmp::Ordering::Equal;
}
assert_eq!(i.cmp(j), expect);
if is_same {
expect = cmp::Ordering::Greater;
}
}
}
}
#[test]
fn protected_access_level_as_str() {
let items = &[
(ProtectedAccessLevel::Developer, "30"),
(ProtectedAccessLevel::Maintainer, "40"),
(ProtectedAccessLevel::Admin, "60"),
(ProtectedAccessLevel::NoAccess, "0"),
];
for (i, s) in items {
assert_eq!(i.as_str(), *s);
}
}
#[test]
fn comma_separated_list_default() {
let csl = CommaSeparatedList::<u64>::default();
assert!(csl.is_empty());
}
#[test]
fn comma_separated_list_vec() {
let csl = CommaSeparatedList::<u64>::new();
let _: &Vec<u64> = &csl;
}
#[test]
fn comma_separated_list_from_iter() {
let _: CommaSeparatedList<_> = iter::once(2).collect();
}
#[test]
fn comma_separated_list_display() {
let csl_one: CommaSeparatedList<_> = iter::once(2).collect();
assert_eq!(format!("{}", csl_one), "2");
let csl_two: CommaSeparatedList<_> = [1, 2].iter().copied().collect();
assert_eq!(format!("{}", csl_two), "1,2");
}
#[test]
fn comma_separated_list_param_value() {
let csl_one: CommaSeparatedList<_> = iter::once(2).collect();
assert_eq!(csl_one.as_value(), "2");
let csl_two: CommaSeparatedList<_> = [1, 2].iter().copied().collect();
assert_eq!(csl_two.as_value(), "1,2");
let csl_str_one: CommaSeparatedList<Cow<str>> = iter::once("one".into()).collect();
assert_eq!(csl_str_one.as_value(), "one");
let csl_str_two: CommaSeparatedList<Cow<str>> =
["one".into(), "two".into()].iter().cloned().collect();
assert_eq!(csl_str_two.as_value(), "one,two");
}
}