feat: orgs, hooks, teams, labels

This commit is contained in:
RobWalt 2023-07-28 08:36:12 +02:00
parent e343273668
commit 01696c7e0e
No known key found for this signature in database
GPG key ID: 333C6AC0CEF0CE68
69 changed files with 55489 additions and 650 deletions

243
Cargo.lock generated
View file

@ -38,6 +38,15 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -65,6 +74,12 @@ version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cc"
version = "1.0.79"
@ -195,6 +210,18 @@ dependencies = [
"once_cell",
]
[[package]]
name = "fast-srgb8"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "forgejo-api-types"
version = "0.1.0"
@ -203,6 +230,8 @@ dependencies = [
"clap",
"color-eyre",
"derive-new",
"hyper",
"palette",
"serde",
"serde-email",
"serde_json",
@ -219,6 +248,39 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"pin-utils",
]
[[package]]
name = "gimli"
version = "0.27.3"
@ -231,6 +293,62 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "http"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "iana-time-zone"
version = "0.1.57"
@ -348,18 +466,90 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "palette"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1641aee47803391405d0a1250e837d2336fdddd18b27f3ddb8c1d80ce8d7f43"
dependencies = [
"approx",
"fast-srgb8",
"palette_derive",
"phf",
"serde",
]
[[package]]
name = "palette_derive"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c02bfa6b3ba8af5434fa0531bf5701f750d983d4260acd6867faca51cdc4484"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
]
[[package]]
name = "percent-encoding"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_macros",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_macros"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.27",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.66"
@ -378,6 +568,21 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@ -446,6 +651,12 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "siphasher"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "strum"
version = "0.25.0"
@ -526,6 +737,23 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
dependencies = [
"autocfg",
"backtrace",
"pin-project-lite",
]
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.37"
@ -568,6 +796,12 @@ dependencies = [
"tracing-core",
]
[[package]]
name = "try-lock"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "unicode-bidi"
version = "0.3.13"
@ -607,6 +841,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"

View file

@ -8,12 +8,12 @@ edition = "2021"
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.3", default-features = false, features = ["derive", "std"] }
serde_json = "1.0"
color-eyre = "0.6"
derive-new = "0.5"
hyper = "0.14.27"
palette = { version = "0.7.2", features = ["serializing"] }
serde = { version = "1.0", features = ["derive"] }
serde-email = "2.1"
strum = { version = "0.25", features = ["derive"] }
url = { version = "2.4", features = ["serde"] }
[dev-dependencies]
serde_json = "1.0"

1
README.md Normal file
View file

@ -0,0 +1 @@
docs: https://codeberg.org/api/swagger#/organization/orgCreateHook

View file

@ -1,14 +0,0 @@
use std::fmt::Display;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Branch {
pub name: String,
}
impl Display for Branch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}

View file

@ -1,13 +0,0 @@
use chrono::{DateTime, Utc};
use serde::Deserialize;
use crate::types::api::user::User;
#[derive(Debug, Deserialize)]
pub struct Comment {
pub body: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub id: usize,
pub user: User,
}

View file

@ -1,12 +0,0 @@
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct CreateCommentOption {
body: String,
}
impl CreateCommentOption {
pub fn new(body: String) -> Self {
Self { body }
}
}

View file

@ -1,16 +0,0 @@
use serde::Serialize;
#[derive(Serialize)]
pub struct CreateForkOption {
name: Option<String>,
organization: Option<String>,
}
impl CreateForkOption {
pub fn same_repo_name() -> Self {
Self {
name: None,
organization: None,
}
}
}

View file

@ -1,42 +0,0 @@
use serde::Serialize;
#[derive(Serialize)]
pub struct CreateIssueOption {
title: String,
body: String,
assignees: Vec<String>,
labels: Vec<usize>,
milestone: Option<usize>,
}
impl CreateIssueOption {
pub fn new(title: String) -> Self {
Self {
title,
body: Default::default(),
assignees: Default::default(),
labels: Default::default(),
milestone: Default::default(),
}
}
pub fn with_body(mut self, body: String) -> Self {
self.body = body;
self
}
pub fn with_assignees(mut self, assignees: Vec<String>) -> Self {
self.assignees = assignees;
self
}
pub fn with_labels(mut self, labels: Vec<usize>) -> Self {
self.labels = labels;
self
}
pub fn with_milestone(mut self, milestone_id: usize) -> Self {
self.milestone.replace(milestone_id);
self
}
}

View file

@ -1,28 +0,0 @@
use serde::Serialize;
#[derive(Serialize)]
pub struct CreateLabelOption {
color: String,
description: String,
name: String,
}
impl CreateLabelOption {
pub fn new(name: String) -> Self {
Self {
color: String::from("#000000"),
description: String::from("No description"),
name,
}
}
pub fn with_color(mut self, color: String) -> Self {
self.color = color;
self
}
pub fn with_description(mut self, description: String) -> Self {
self.description = description;
self
}
}

View file

@ -1,23 +0,0 @@
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct CreateMilestoneOption {
title: String,
due_on: Option<String>,
description: Option<String>,
}
impl CreateMilestoneOption {
pub fn new(title: String) -> Self {
Self {
title,
due_on: Default::default(),
description: Default::default(),
}
}
pub fn with_description(mut self, description: String) -> Self {
self.description.replace(description);
self
}
}

View file

@ -1,46 +0,0 @@
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct CreatePullRequestOption {
assignees: Vec<String>,
base: String,
body: String,
head: String,
labels: Vec<usize>,
title: String,
milestone: Option<usize>,
}
impl CreatePullRequestOption {
pub fn new(title: String, from: String, to: String) -> Self {
Self {
title,
head: from,
base: to,
assignees: Default::default(),
body: Default::default(),
labels: Default::default(),
milestone: Default::default(),
}
}
pub fn with_assignees(mut self, assignees: Vec<String>) -> Self {
self.assignees = assignees;
self
}
pub fn with_description(mut self, description: String) -> Self {
self.body = description;
self
}
pub fn with_labels(mut self, labels: Vec<usize>) -> Self {
self.labels = labels;
self
}
pub fn with_milestone(mut self, milestone_id: usize) -> Self {
self.milestone.replace(milestone_id);
self
}
}

View file

@ -1,47 +0,0 @@
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct CreateRepoOption {
default_branch: String,
description: String,
name: String,
private: bool,
readme: String,
}
impl CreateRepoOption {
pub fn new(name: String) -> Self {
Self {
default_branch: String::from("main"),
description: Default::default(),
name,
private: true,
readme: Default::default(),
}
}
pub fn with_default_branch(mut self, default_branch: String) -> Self {
self.default_branch = default_branch;
self
}
pub fn with_description(mut self, description: String) -> Self {
self.description = description;
self
}
pub fn private(mut self) -> Self {
self.private = true;
self
}
pub fn public(mut self) -> Self {
self.private = false;
self
}
pub fn with_readme(mut self, readme: String) -> Self {
self.readme = readme;
self
}
}

View file

@ -1,7 +0,0 @@
pub mod create_comment_option;
pub mod create_fork_option;
pub mod create_issue_options;
pub mod create_label_options;
pub mod create_milestone_option;
pub mod create_pull_request_option;
pub mod create_repo_options;

View file

@ -0,0 +1,47 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::types::misc::color::Color;
use crate::types::misc::exclusive::Exclusive;
/// CreateLabelOption represents options for creating a label.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CreateLabelOption {
/// The color of the label (in hexadecimal format).
pub color: Color,
/// The description of the label.
pub description: String,
/// Indicates whether the label is exclusive.
pub exclusive: Exclusive,
/// The name of the label.
pub name: String,
}
impl Display for CreateLabelOption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CreateLabelOption: {}", self.name)
}
}
#[cfg(test)]
mod tests {
use palette::rgb::Rgb;
use crate::types::api::creation::label::CreateLabelOption;
use crate::types::misc::color::Color;
use crate::types::misc::exclusive::Exclusive;
#[test]
fn deserialize_create_label_option() {
let json_data = include_str!("../../../../test_data/example_create_label.json");
let option: CreateLabelOption = serde_json::from_str(&json_data).unwrap();
let expected = CreateLabelOption {
color: Color(Rgb::new(0x00, 0xaa, 0xbb)),
description: "CreateLabelOption options for creating a label".to_string(),
exclusive: Exclusive::No,
name: "example_label".to_string(),
};
assert_eq!(option, expected);
}
}

View file

@ -0,0 +1,2 @@
pub mod label;
pub mod organization;

View file

@ -0,0 +1,60 @@
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use crate::types::misc::team_access::RepoAdminCanChangeTeamAccess;
use crate::types::misc::url::OptionalUrl;
use crate::types::misc::visibility::Visibility;
/// CreateOrgOption represents options for creating an organization
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateOrgOption {
/// The organization's description
pub description: String,
/// Full name of the organization
pub full_name: String,
/// Location of the organization as a string
pub location: String,
/// Boolean flag indicating if repo admin can change team access
pub repo_admin_change_team_access: RepoAdminCanChangeTeamAccess,
/// The organization's username
pub username: String,
/// Visibility of the organization
pub visibility: Visibility,
/// The organization's website URL (optional)
pub website: OptionalUrl,
}
impl Display for CreateOrgOption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CreateOrgOption: {}", self.full_name)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use url::Url;
use crate::types::api::creation::organization::CreateOrgOption;
use crate::types::misc::team_access::RepoAdminCanChangeTeamAccess;
use crate::types::misc::url::OptionalUrl;
use crate::types::misc::visibility::Visibility;
#[test]
fn deserialize_works() {
let data = include_str!("../../../../test_data/example_create_organization.json");
let org_option: CreateOrgOption = serde_json::from_str(data).unwrap();
let expected = CreateOrgOption {
description: String::from("Sample organization"),
full_name: String::from("SampleOrg"),
location: String::from("Sample City, Country"),
repo_admin_change_team_access: RepoAdminCanChangeTeamAccess::Yes,
username: String::from("sample_org"),
visibility: Visibility::Public,
website: OptionalUrl::Some(Url::from_str("https://sample.org").unwrap()),
};
assert_eq!(org_option, expected);
}
}

View file

@ -0,0 +1,57 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::types::misc::active::ActiveStatus;
use crate::types::misc::header::Header;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EditHookOption {
/// Description of the options when modifying a hook
description: String,
/// Boolean flag indicating if the hook is active
active: ActiveStatus,
/// Authorization header for the hook (if required)
authorization_header: Header,
/// Branch filter for the hook
branch_filter: String,
/// Configuration options for the hook
config: HashMap<String, String>,
/// Events that trigger the hook
events: Vec<String>,
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use hyper::http::{HeaderName, HeaderValue};
use crate::types::api::edit::hook::EditHookOption;
use crate::types::misc::active::ActiveStatus;
use crate::types::misc::header::Header;
#[test]
fn deserialize_works() {
let data = include_str!("../../../../test_data/example_edit_hook.json");
let edit_hook_option: EditHookOption = serde_json::from_str(data).unwrap();
// Example usage:
let expected = EditHookOption {
description: "Options when modifying a hook".to_string(),
active: ActiveStatus::Active,
authorization_header: Header(hyper::HeaderMap::from_iter(std::iter::once((
HeaderName::from_static("bearer"),
HeaderValue::from_static("TOKEN"),
)))),
branch_filter: "main".to_string(),
config: {
let mut config = HashMap::new();
config.insert("key1".to_string(), "value1".to_string());
config.insert("key2".to_string(), "value2".to_string());
config
},
events: vec!["push".to_string(), "pull_request".to_string()],
};
assert_eq!(edit_hook_option, expected);
}
}

View file

@ -0,0 +1,47 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::types::misc::color::Color;
use crate::types::misc::exclusive::Exclusive;
/// CreateLabelOption represents options for creating a label.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EditLabelOption {
/// The color of the label (in hexadecimal format).
pub color: Color,
/// The description of the label.
pub description: String,
/// Indicates whether the label is exclusive.
pub exclusive: Exclusive,
/// The name of the label.
pub name: String,
}
impl Display for EditLabelOption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "EditLabelOption: {}", self.name)
}
}
#[cfg(test)]
mod tests {
use palette::rgb::Rgb;
use crate::types::api::creation::label::CreateLabelOption;
use crate::types::misc::color::Color;
use crate::types::misc::exclusive::Exclusive;
#[test]
fn deserialize_edit_label_option() {
let json_data = include_str!("../../../../test_data/example_edit_label.json");
let option: CreateLabelOption = serde_json::from_str(&json_data).unwrap();
let expected = CreateLabelOption {
color: Color(Rgb::new(0x00, 0xaa, 0xbb)),
description: "EditLabelOption options for updating a label".to_string(),
exclusive: Exclusive::No,
name: "example_label".to_string(),
};
assert_eq!(option, expected);
}
}

View file

@ -0,0 +1,3 @@
pub mod hook;
pub mod label;
pub mod organization;

View file

@ -0,0 +1,57 @@
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use crate::types::misc::team_access::RepoAdminCanChangeTeamAccess;
use crate::types::misc::url::OptionalUrl;
use crate::types::misc::visibility::Visibility;
/// EditOrgOption represents options for editing an organization
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EditOrgOption {
/// The organization's description
pub description: String,
/// Full name of the organization
pub full_name: String,
/// Location of the organization as a string
pub location: String,
/// Boolean flag indicating if repo admin can change team access
pub repo_admin_change_team_access: RepoAdminCanChangeTeamAccess,
/// Visibility of the organization
pub visibility: Visibility,
/// The organization's website URL (optional)
pub website: OptionalUrl,
}
impl Display for EditOrgOption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "EditOrgOption: {}", self.full_name)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use url::Url;
use crate::types::api::edit::organization::EditOrgOption;
use crate::types::misc::team_access::RepoAdminCanChangeTeamAccess;
use crate::types::misc::url::OptionalUrl;
use crate::types::misc::visibility::Visibility;
#[test]
fn deserialize_works() {
let data = include_str!("../../../../test_data/example_edit_organization.json");
let edit_org_option: EditOrgOption = serde_json::from_str(data).unwrap();
let expected = EditOrgOption {
description: String::from("Updated organization"),
full_name: String::from("UpdatedOrg"),
location: String::from("Updated City, Country"),
repo_admin_change_team_access: RepoAdminCanChangeTeamAccess::No,
visibility: Visibility::Private,
website: OptionalUrl::Some(Url::from_str("https://updated.org").unwrap()),
};
assert_eq!(edit_org_option, expected);
}
}

View file

@ -1,32 +0,0 @@
use serde::Serialize;
use crate::types::api::issue::Issue;
use crate::types::api::state_type::StateType;
#[derive(Debug, Clone, Serialize, Default)]
pub struct EditIssueOption {
pub assignees: Option<Vec<String>>,
pub body: Option<String>,
pub state: Option<StateType>,
pub title: Option<String>,
}
impl EditIssueOption {
pub fn from_issue(issue: &Issue) -> Self {
Self {
assignees: issue.assignees.as_ref().map(|assignees| {
assignees
.iter()
.map(|assignee| assignee.login.to_owned())
.collect::<Vec<_>>()
}),
body: Some(issue.body.clone()),
state: issue
.pull_request
.as_ref()
.map(|pr_meta| (!pr_meta.merged).then_some(issue.state))
.unwrap_or(Some(issue.state)),
title: Some(issue.title.clone()),
}
}
}

View file

@ -1,20 +0,0 @@
use serde::Serialize;
use crate::types::api::label::Label;
#[derive(Debug, Clone, Serialize, Default)]
pub struct EditLabelOption {
pub name: Option<String>,
pub description: Option<String>,
pub color: Option<String>,
}
impl EditLabelOption {
pub fn from_label(label: &Label) -> Self {
Self {
name: Some(label.name.to_string()),
description: Some(label.description.to_string()),
color: Some(label.color.to_string()),
}
}
}

View file

@ -1,24 +0,0 @@
use chrono::{DateTime, Utc};
use serde::Serialize;
use crate::types::api::milestone::Milestone;
use crate::types::api::state_type::StateType;
#[derive(Debug, Clone, Serialize, Default)]
pub struct EditMilestoneOption {
pub description: Option<String>,
pub state: Option<StateType>,
pub title: Option<String>,
pub due_on: Option<DateTime<Utc>>,
}
impl EditMilestoneOption {
pub fn from_milestone(milestone: &Milestone) -> Self {
Self {
description: milestone.description.clone(),
due_on: milestone.due_on,
state: Some(milestone.state),
title: Some(milestone.title.clone()),
}
}
}

View file

@ -1,30 +0,0 @@
use serde::Serialize;
use crate::types::api::pull_request::PullRequest;
use crate::types::api::state_type::StateType;
#[derive(Debug, Clone, Serialize, Default)]
pub struct EditPullRequestOption {
pub assignees: Option<Vec<String>>,
pub body: Option<String>,
pub state: Option<StateType>,
pub title: Option<String>,
pub labels: Option<Vec<usize>>,
}
impl EditPullRequestOption {
pub fn from_pull_request(pr: &PullRequest) -> Self {
Self {
assignees: pr.assignees.as_ref().map(|assignees| {
assignees
.iter()
.map(|assignee| assignee.login.to_owned())
.collect::<Vec<_>>()
}),
body: Some(pr.body.clone()),
state: (!pr.merged).then_some(pr.state),
title: Some(pr.title.clone()),
labels: Some(pr.labels.iter().map(|label| label.id).collect::<Vec<_>>()),
}
}
}

View file

@ -1,4 +0,0 @@
pub mod edit_issue_option;
pub mod edit_label_option;
pub mod edit_milestone_option;
pub mod edit_pull_request_option;

View file

@ -1,4 +0,0 @@
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct FollowersInfo {}

View file

@ -1,4 +0,0 @@
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct FollowingInfo {}

80
src/types/api/hook.rs Normal file
View file

@ -0,0 +1,80 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Display;
use crate::types::misc::active::ActiveStatus;
use crate::types::misc::header::Header;
/// Hook represents a web hook when one repository is changed
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Hook {
/// Boolean flag indicating if the hook is active
pub active: ActiveStatus,
/// Authorization header for the Hook
///
/// This deviates a bit from the swagger docs and I'm not sure how it's even used correctly or
/// what it does
pub authorization_header: Header,
/// Configuration options for the hook
pub config: HashMap<String, String>,
/// Timestamp when the hook was created
pub created_at: DateTime<Utc>,
/// Events that trigger the hook
pub events: Vec<String>,
/// Unique ID of the hook
pub id: usize,
// 🚧🚧 TODO 🚧🚧 : enum?
/// Type of the hook
pub r#type: String,
/// Timestamp when the hook was last updated
pub updated_at: DateTime<Utc>,
}
impl Display for Hook {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Hook ID: {}", self.id)
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::str::FromStr;
use chrono::DateTime;
use hyper::http::{HeaderName, HeaderValue};
use crate::types::api::hook::Hook;
use crate::types::misc::active::ActiveStatus;
use crate::types::misc::header::Header;
#[test]
fn deserialize_works() {
let data = include_str!("../../../test_data/example_hook.json");
let hook: Hook = serde_json::from_str(data).unwrap();
let expected = Hook {
active: ActiveStatus::Active,
authorization_header: Header(hyper::HeaderMap::from_iter(std::iter::once((
HeaderName::from_static("bearer"),
HeaderValue::from_static("TOKEN"),
)))),
config: {
let mut config = HashMap::new();
config.insert(
String::from("url"),
String::from("https://example.com/webhook"),
);
config.insert(String::from("content_type"), String::from("json"));
config
},
created_at: DateTime::from_str("2023-07-27T12:34:56Z").unwrap(),
events: vec![String::from("push"), String::from("pull_request")],
id: 123456,
r#type: String::from("web"),
updated_at: DateTime::from_str("2023-07-27T14:45:56Z").unwrap(),
};
assert_eq!(hook, expected);
}
}

View file

@ -1,32 +0,0 @@
use std::fmt::Display;
use chrono::{DateTime, Utc};
use serde::Deserialize;
use crate::types::api::label::Label;
use crate::types::api::milestone::Milestone;
use crate::types::api::pull_request_meta::PullRequestMeta;
use crate::types::api::state_type::StateType;
use crate::types::api::user::User;
#[derive(Debug, Clone, Deserialize)]
pub struct Issue {
pub title: String,
pub number: usize,
pub labels: Vec<Label>,
pub assignees: Option<Vec<User>>,
pub body: String,
pub state: StateType,
pub pull_request: Option<PullRequestMeta>,
pub milestone: Option<Milestone>,
pub closed_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub due_date: Option<DateTime<Utc>>,
pub updated_at: DateTime<Utc>,
}
impl Display for Issue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "#{} {}", self.number, self.title)
}
}

View file

@ -1,6 +0,0 @@
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct IssueLabelsOption {
pub labels: Vec<usize>,
}

View file

@ -1,13 +1,26 @@
use std::fmt::Display;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
use crate::types::misc::color::Color;
use crate::types::misc::exclusive::Exclusive;
/// Label represents a label attached to an issue or a PR.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Label {
pub color: String,
/// The color of the label (in hexadecimal format).
pub color: Color,
/// The description of the label.
pub description: String,
pub name: String,
/// Indicates whether the label is exclusive.
pub exclusive: Exclusive,
/// The unique ID of the label.
pub id: usize,
/// The name of the label.
pub name: String,
/// The URL of the label.
pub url: Url,
}
impl Display for Label {
@ -15,3 +28,30 @@ impl Display for Label {
write!(f, "{}", self.name)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use palette::rgb::Rgb;
use url::Url;
use crate::types::api::label::Label;
use crate::types::misc::color::Color;
use crate::types::misc::exclusive::Exclusive;
#[test]
fn deserialize_label() {
let data = include_str!("../../../test_data/example_label.json");
let label: Label = serde_json::from_str(&data).unwrap();
let expected = Label {
color: Color(Rgb::from_str("00aabb").unwrap()),
description: "Label a label to an issue or a pr".to_string(),
exclusive: Exclusive::No,
id: 42,
name: "bug".to_string(),
url: Url::from_str("https://example.com/labels/bug").unwrap(),
};
assert_eq!(label, expected);
}
}

View file

@ -1,23 +0,0 @@
use std::fmt::Display;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::types::api::state_type::StateType;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Milestone {
pub id: usize,
pub title: String,
pub due_on: Option<DateTime<Utc>>,
pub state: StateType,
pub open_issues: usize,
pub closed_issues: usize,
pub description: Option<String>,
}
impl Display for Milestone {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.title)
}
}

View file

@ -1,18 +1,7 @@
pub mod branch;
pub mod comment;
pub mod create_options;
pub mod edit_options;
pub mod followers_info;
pub mod following_info;
pub mod issue;
pub mod issue_labels_option;
pub mod creation;
pub mod edit;
pub mod hook;
pub mod label;
pub mod milestone;
pub mod notification;
pub mod privacy_type;
pub mod pull_request;
pub mod pull_request_meta;
pub mod repository;
pub mod search_results;
pub mod state_type;
pub mod organization;
pub mod team;
pub mod user;

View file

@ -1,5 +0,0 @@
pub mod notification_count;
pub mod notification_state_type;
pub mod notification_subject;
pub mod notification_thread;
pub mod notification_type;

View file

@ -1,6 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct NotificationCount {
new: usize,
}

View file

@ -1,29 +0,0 @@
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter, EnumString};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ValueEnum, Display, EnumIter, EnumString)]
#[strum(serialize_all = "lowercase")]
#[serde(rename(serialize = "lowercase"))]
pub enum NotificationStateType {
Unread,
Read,
Pinned,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ValueEnum, Display, EnumIter, EnumString)]
#[strum(serialize_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum NotificationStateTypeApi {
Closed,
Open,
All,
Merged,
}
impl NotificationStateType {
pub fn available_for_choosing() -> [Self; 3] {
use NotificationStateType::*;
[Unread, Read, Pinned]
}
}

View file

@ -1,15 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::types::api::notification::notification_state_type::NotificationStateTypeApi;
use crate::types::api::notification::notification_type::NotificationSubjectType;
#[derive(Debug, Serialize, Deserialize)]
pub struct NotificationSubject {
pub html_url: String,
pub latest_comment_html_url: String,
pub latest_comment_url: String,
pub state: NotificationStateTypeApi,
pub title: String,
#[serde(rename = "type")]
pub notify_type: NotificationSubjectType,
}

View file

@ -1,29 +0,0 @@
use std::fmt::Display;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::types::api::notification::notification_subject::NotificationSubject;
use crate::types::api::repository::Repository;
#[derive(Debug, Serialize, Deserialize)]
pub struct NotificationThread {
pub id: usize,
pub pinned: bool,
pub repository: Repository,
pub subject: NotificationSubject,
pub unread: bool,
pub updated_at: DateTime<Utc>,
pub url: String,
}
impl Display for NotificationThread {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(if self.unread { "📕 " } else { "📖 " })?;
if self.pinned {
f.write_str("📌 ")?;
}
f.write_str(self.subject.title.as_str())?;
Ok(())
}
}

View file

@ -1,19 +0,0 @@
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter, EnumString};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ValueEnum, Display, EnumIter, EnumString)]
#[strum(serialize_all = "lowercase")]
#[serde(rename(serialize = "lowercase"))]
pub enum NotificationSubjectType {
Issue,
Pull,
Commit,
Repository,
}
impl NotificationSubjectType {
pub fn available_for_choosing() -> [Self; 4] {
use NotificationSubjectType::*;
[Issue, Pull, Commit, Repository]
}
}

View file

@ -0,0 +1,68 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::types::misc::team_access::RepoAdminCanChangeTeamAccess;
use crate::types::misc::url::OptionalUrl;
/// Organization represents an organization
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Organization {
/// URL to the organization's avatar
pub avatar_url: Url,
/// The organization's description (optional)
pub description: String,
/// Full name of the organization (optional)
pub full_name: String,
/// An unique ID
pub id: usize,
/// Location of the organization as a string (optional)
pub location: String,
/// Name of the organization
pub name: String,
/// Boolean flag indicating if repo admin can change team access
pub repo_admin_change_team_access: RepoAdminCanChangeTeamAccess,
/// The organization's username
pub username: String,
/// The organization's website URL (optional)
pub website: OptionalUrl,
}
impl Display for Organization {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use url::Url;
use crate::types::api::organization::Organization;
use crate::types::misc::team_access::RepoAdminCanChangeTeamAccess;
use crate::types::misc::url::OptionalUrl;
#[test]
fn deserialize_works() {
let data = include_str!("../../../test_data/example_organization.json");
let org: Organization = serde_json::from_str(&data).unwrap();
let expected = Organization {
avatar_url: Url::from_str(
"https://codeberg.org/avatars/9efc314b65237d5d646e1b817372afc6",
)
.unwrap(),
description: String::from(""),
full_name: String::from(""),
id: 77294,
location: String::from(""),
name: String::from("---"),
repo_admin_change_team_access: RepoAdminCanChangeTeamAccess::Yes,
username: String::from("---"),
website: OptionalUrl::None,
};
assert_eq!(org, expected);
}
}

View file

@ -1,9 +0,0 @@
use clap::ValueEnum;
use strum::{Display, EnumIter, EnumString};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Display, EnumString, ValueEnum, EnumIter)]
pub enum Privacy {
#[default]
Private,
Public,
}

View file

@ -1,32 +0,0 @@
use std::fmt::Display;
use chrono::{DateTime, Utc};
use serde::Deserialize;
use crate::types::api::label::Label;
use crate::types::api::milestone::Milestone;
use crate::types::api::state_type::StateType;
use crate::types::api::user::User;
#[derive(Debug, Clone, Deserialize)]
pub struct PullRequest {
pub title: String,
pub body: String,
pub number: usize,
pub labels: Vec<Label>,
pub state: StateType,
pub assignees: Option<Vec<User>>,
pub milestone: Option<Milestone>,
pub merged: bool,
pub closed_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub due_date: Option<DateTime<Utc>>,
pub merged_at: Option<DateTime<Utc>>,
pub updated_at: DateTime<Utc>,
}
impl Display for PullRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "#{} {}", self.number, self.title)
}
}

View file

@ -1,7 +0,0 @@
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct PullRequestMeta {
pub merged: bool,
pub merged_at: Option<String>,
}

View file

@ -1,11 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::types::api::user::User;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Repository {
pub name: String,
pub owner: User,
pub stars_count: usize,
pub ssh_url: String,
}

View file

@ -1,7 +0,0 @@
use serde::Deserialize;
#[derive(Deserialize, Debug)]
pub struct SearchResults<T> {
pub data: T,
pub ok: bool,
}

View file

@ -1,28 +0,0 @@
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter, EnumString};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ValueEnum, Display, EnumIter, EnumString)]
#[strum(serialize_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum StateType {
Closed,
Open,
All,
}
impl StateType {
pub fn available_for_choosing() -> [Self; 2] {
use StateType::*;
[Closed, Open]
}
pub fn is_done(&self) -> bool {
use StateType::*;
match self {
Closed => true,
Open => false,
All => false,
}
}
}

83
src/types/api/team.rs Normal file
View file

@ -0,0 +1,83 @@
use std::collections::HashMap;
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::types::api::organization::Organization;
use crate::types::misc::can_create_org_repo::CanCreateOrgRepo;
use crate::types::misc::includes_all_repositories::IncludesAllRepositories;
use crate::types::misc::team_permissions::TeamPermission;
/// Team represents a team in an organization
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Team {
/// Whether the team can create organization repositories
pub can_create_org_repo: CanCreateOrgRepo,
/// Description of the team
pub description: String,
/// ID of the team
pub id: usize,
/// Whether the team includes all org repositories
pub includes_all_repositories: IncludesAllRepositories,
/// Name of the team
pub name: String,
/// Organization the team belongs to
pub organization: Organization,
/// Permission level of the team
pub permission: TeamPermission,
/// Units that the team has access to
pub units: Vec<String>,
/// Units and their corresponding permission as a map
pub units_map: HashMap<String, TeamPermission>,
}
impl Display for Team {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Team: {}", self.name)
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use crate::types::api::organization::Organization;
use crate::types::api::team::Team;
use crate::types::misc::can_create_org_repo::CanCreateOrgRepo;
use crate::types::misc::includes_all_repositories::IncludesAllRepositories;
use crate::types::misc::team_permissions::TeamPermission;
#[test]
fn deserialize_team_works() {
let data = include_str!("../../../test_data/example_team.json");
let team: Team = serde_json::from_str(data).unwrap();
let example_org: Organization =
serde_json::from_str(include_str!("../../../test_data/example_organization.json"))
.unwrap();
let expected = Team {
can_create_org_repo: CanCreateOrgRepo::Yes,
description: String::from("Example team description"),
id: 12345,
includes_all_repositories: IncludesAllRepositories::No,
name: String::from("Example Team"),
organization: example_org,
permission: TeamPermission::Admin,
//🚧🚧 TODO 🚧🚧 : Units as enum?
units: vec![
"repo.code".to_string(),
"repo.issues".to_string(),
"repo.ext_issues".to_string(),
],
units_map: {
let mut map = HashMap::new();
map.insert("repo.code".to_string(), TeamPermission::Read);
map.insert("repo.issues".to_string(), TeamPermission::Write);
map.insert("repo.ext_issues".to_string(), TeamPermission::None);
map
},
};
assert_eq!(team, expected);
}
}

View file

@ -8,6 +8,7 @@ use url::Url;
use crate::types::misc::active::ActiveStatus;
use crate::types::misc::admin::AdminStatus;
use crate::types::misc::locale::Locale;
use crate::types::misc::url::OptionalUrl;
use crate::types::misc::visibility::Visibility;
/// User represents a user
@ -43,8 +44,10 @@ pub struct User {
pub login: String,
/// the user's authentication sign-in name.
pub login_name: String,
// 🚧🚧 TODO 🚧🚧 : Enumify
/// is user banned
pub prohibit_login: bool,
// 🚧🚧 TODO 🚧🚧 : Enumify
/// is user restricted
pub restricted: bool,
/// stars the user gave other repos
@ -54,7 +57,7 @@ pub struct User {
/// visibility of user
pub visibility: Visibility,
/// personal website (optional)
pub website: Url,
pub website: OptionalUrl,
}
impl Display for User {
@ -75,11 +78,12 @@ mod tests {
use crate::types::misc::active::ActiveStatus;
use crate::types::misc::admin::AdminStatus;
use crate::types::misc::locale::Locale;
use crate::types::misc::url::OptionalUrl;
use crate::types::misc::visibility::Visibility;
#[test]
fn deserialize_works() {
let data = include_str!("../../test_data/example_user.json");
let data = include_str!("../../../test_data/example_user.json");
let user: User = serde_json::from_str(&data).unwrap();
let expected = User {
active: ActiveStatus::Active,
@ -102,7 +106,7 @@ mod tests {
starred_repos_count: 0,
username: String::from("ExampleUser"),
visibility: Visibility::Public,
website: Url::from_str("https://example.com").unwrap(),
website: OptionalUrl::Some(Url::from_str("https://example.com").unwrap()),
};
assert_eq!(user, expected);
}

View file

@ -0,0 +1,87 @@
use clap::ValueEnum;
use strum::{Display, EnumIs, EnumIter, EnumString};
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Display, EnumIter, EnumString, EnumIs)]
pub enum CanCreateOrgRepo {
Yes,
No,
}
mod serde {
use serde::de::Visitor;
use serde::{Deserialize, Serialize};
use crate::types::misc::can_create_org_repo::CanCreateOrgRepo;
impl Serialize for CanCreateOrgRepo {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_bool(matches!(*self, CanCreateOrgRepo::Yes))
}
}
struct CanCreateOrgRepoVisitor;
impl<'de> Visitor<'de> for CanCreateOrgRepoVisitor {
type Value = CanCreateOrgRepo;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a boolean value (true/false)")
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v {
Ok(CanCreateOrgRepo::Yes)
} else {
Ok(CanCreateOrgRepo::No)
}
}
}
impl<'de> Deserialize<'de> for CanCreateOrgRepo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_bool(CanCreateOrgRepoVisitor)
}
}
}
#[cfg(test)]
mod exclusive {
use crate::types::misc::can_create_org_repo::CanCreateOrgRepo;
#[test]
fn deserialize_true() {
let input = "true";
let val: CanCreateOrgRepo = serde_json::from_str(input).unwrap();
assert_eq!(CanCreateOrgRepo::Yes, val);
}
#[test]
fn deserialize_false() {
let input = "false";
let val: CanCreateOrgRepo = serde_json::from_str(input).unwrap();
assert_eq!(CanCreateOrgRepo::No, val);
}
#[test]
fn serialize_true() {
let input = CanCreateOrgRepo::Yes;
let val = serde_json::to_string(&input).unwrap();
assert_eq!(val.as_str(), "true");
}
#[test]
fn serialize_false() {
let input = CanCreateOrgRepo::No;
let val = serde_json::to_string(&input).unwrap();
assert_eq!(val.as_str(), "false");
}
}

108
src/types/misc/color.rs Normal file
View file

@ -0,0 +1,108 @@
use std::ops::{Deref, DerefMut};
use palette::rgb::Rgb;
use palette::Srgb;
use serde::de::Visitor;
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq)]
pub struct Color(pub(crate) Rgb<Srgb, u8>);
impl Deref for Color {
type Target = Rgb<Srgb, u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Color {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Serialize for Color {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Convert the RGB color to a six-digit hexadecimal string
let hex_string =
format!("#{:02X}{:02X}{:02X}", self.red, self.green, self.blue).to_lowercase();
serializer.serialize_str(&hex_string)
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ColorVisitor;
impl<'de> Visitor<'de> for ColorVisitor {
type Value = Color;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a hex color string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
// convert to simplified hex string
let start_char = if v.starts_with('#') { 1 } else { 0 };
let hex_string = v[start_char..].to_lowercase();
// Parse the hexadecimal string to an RGB color
let r = u8::from_str_radix(&hex_string[0..2], 16).map_err(Error::custom)?;
let g = u8::from_str_radix(&hex_string[2..4], 16).map_err(Error::custom)?;
let b = u8::from_str_radix(&hex_string[4..6], 16).map_err(Error::custom)?;
Ok(Color(Rgb::new(r, g, b)))
}
}
deserializer.deserialize_str(ColorVisitor)
}
}
#[cfg(test)]
mod color {
use palette::rgb::Rgb;
use crate::types::misc::color::Color;
#[test]
fn color_serialize() {
// Example of serializing and deserializing
let rgb_color = Color(Rgb::new(100, 50, 200));
let rgb_color_str = "\"#6432c8\"";
let serialized = serde_json::to_string(&rgb_color).unwrap();
assert_eq!(serialized, rgb_color_str);
}
#[test]
fn color_deserialize() {
// Example of serializing and deserializing
let rgb_color = Color(Rgb::new(100, 50, 200));
let rgb_color_str = "\"#6432c8\"";
let deserialized: Color = serde_json::from_str(&rgb_color_str).unwrap();
assert_eq!(deserialized, rgb_color);
}
#[test]
#[should_panic]
fn color_deserialize_panic() {
// Example of serializing and deserializing
let rgb_color = Color(Rgb::new(100, 50, 200));
let rgb_color_str = "\"##6432c8\"";
let deserialized: Color = serde_json::from_str(&rgb_color_str).unwrap();
assert_eq!(deserialized, rgb_color);
}
}

View file

@ -0,0 +1,87 @@
use clap::ValueEnum;
use strum::{Display, EnumIs, EnumIter, EnumString};
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Display, EnumIter, EnumString, EnumIs)]
pub enum Exclusive {
Yes,
No,
}
mod serde {
use serde::de::Visitor;
use serde::{Deserialize, Serialize};
use crate::types::misc::exclusive::Exclusive;
impl Serialize for Exclusive {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_bool(matches!(*self, Exclusive::Yes))
}
}
struct ObjectStatusVisitor;
impl<'de> Visitor<'de> for ObjectStatusVisitor {
type Value = Exclusive;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a boolean value (true/false)")
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v {
Ok(Exclusive::Yes)
} else {
Ok(Exclusive::No)
}
}
}
impl<'de> Deserialize<'de> for Exclusive {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_bool(ObjectStatusVisitor)
}
}
}
#[cfg(test)]
mod exclusive {
use crate::types::misc::exclusive::Exclusive;
#[test]
fn deserialize_true() {
let input = "true";
let val: Exclusive = serde_json::from_str(input).unwrap();
assert_eq!(Exclusive::Yes, val);
}
#[test]
fn deserialize_false() {
let input = "false";
let val: Exclusive = serde_json::from_str(input).unwrap();
assert_eq!(Exclusive::No, val);
}
#[test]
fn serialize_true() {
let input = Exclusive::Yes;
let val = serde_json::to_string(&input).unwrap();
assert_eq!(val.as_str(), "true");
}
#[test]
fn serialize_false() {
let input = Exclusive::No;
let val = serde_json::to_string(&input).unwrap();
assert_eq!(val.as_str(), "false");
}
}

104
src/types/misc/header.rs Normal file
View file

@ -0,0 +1,104 @@
use std::ops::{Deref, DerefMut};
use hyper::header::{HeaderName, HeaderValue};
use serde::ser::SerializeMap;
use serde::{Deserialize, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Header(pub hyper::HeaderMap);
impl Deref for Header {
type Target = hyper::HeaderMap;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Header {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
// Serialization
impl Serialize for Header {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.iter().count()))?;
for (name, value) in self.iter() {
let name_str = name.as_str();
let value_str = String::from_utf8_lossy(value.as_bytes()).to_string();
map.serialize_entry(&name_str, &value_str)?;
}
map.end()
}
}
// Deserialization
impl<'de> Deserialize<'de> for Header {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct HeaderMapVisitor;
impl<'de> serde::de::Visitor<'de> for HeaderMapVisitor {
type Value = Header;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a JSON object with header key-value pairs")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let mut headers = hyper::HeaderMap::new();
while let Some((key, value)) = map.next_entry::<String, String>()? {
let name_obj =
HeaderName::from_bytes(key.as_bytes()).map_err(serde::de::Error::custom)?;
let value_obj =
HeaderValue::from_str(&value).map_err(serde::de::Error::custom)?;
headers.insert(name_obj, value_obj);
}
Ok(Header(headers))
}
}
deserializer.deserialize_map(HeaderMapVisitor)
}
}
#[cfg(test)]
mod header {
use hyper::http::{HeaderName, HeaderValue};
use crate::types::misc::header::Header;
#[test]
fn deserialize_works() {
let data = include_str!("../../../test_data/example_header_map.json");
let header: Header = serde_json::from_str(data).unwrap();
// Create a new HeaderMap
let mut headers = hyper::HeaderMap::new();
// Add some example headers to the map
headers.insert(
HeaderName::from_static("content-type"),
HeaderValue::from_static("application/json"),
);
headers.insert(
HeaderName::from_static("authorization"),
HeaderValue::from_static("Bearer ABCDEFG123456789"),
);
let expected = Header(headers);
assert_eq!(header, expected);
}
}

View file

@ -0,0 +1,88 @@
use clap::ValueEnum;
use strum::{Display, EnumIs, EnumIter, EnumString};
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Display, EnumIter, EnumString, EnumIs)]
pub enum IncludesAllRepositories {
Yes,
No,
}
mod serde {
use serde::de::Visitor;
use serde::{Deserialize, Serialize};
use crate::types::misc::includes_all_repositories::IncludesAllRepositories;
impl Serialize for IncludesAllRepositories {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_bool(matches!(*self, IncludesAllRepositories::Yes))
}
}
struct IncludesAllRepositoriesVisitor;
impl<'de> Visitor<'de> for IncludesAllRepositoriesVisitor {
type Value = IncludesAllRepositories;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a boolean value (true/false)")
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v {
Ok(IncludesAllRepositories::Yes)
} else {
Ok(IncludesAllRepositories::No)
}
}
}
impl<'de> Deserialize<'de> for IncludesAllRepositories {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_bool(IncludesAllRepositoriesVisitor)
}
}
}
#[cfg(test)]
mod exclusive {
use crate::types::misc::includes_all_repositories::IncludesAllRepositories;
#[test]
fn deserialize_true() {
let input = "true";
let val: IncludesAllRepositories = serde_json::from_str(input).unwrap();
assert_eq!(IncludesAllRepositories::Yes, val);
}
#[test]
fn deserialize_false() {
let input = "false";
let val: IncludesAllRepositories = serde_json::from_str(input).unwrap();
assert_eq!(IncludesAllRepositories::No, val);
}
#[test]
fn serialize_true() {
let input = IncludesAllRepositories::Yes;
let val = serde_json::to_string(&input).unwrap();
assert_eq!(val.as_str(), "true");
}
#[test]
fn serialize_false() {
let input = IncludesAllRepositories::No;
let val = serde_json::to_string(&input).unwrap();
assert_eq!(val.as_str(), "false");
}
}

View file

@ -1,4 +1,12 @@
pub mod active;
pub mod admin;
pub mod can_create_org_repo;
pub mod color;
pub mod exclusive;
pub mod header;
pub mod includes_all_repositories;
pub mod locale;
pub mod team_access;
pub mod team_permissions;
pub mod url;
pub mod visibility;

View file

@ -0,0 +1,80 @@
use serde::de::Visitor;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepoAdminCanChangeTeamAccess {
Yes,
No,
}
impl Serialize for RepoAdminCanChangeTeamAccess {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_bool(matches!(*self, RepoAdminCanChangeTeamAccess::Yes))
}
}
struct TeamAccessVisitor;
impl<'de> Visitor<'de> for TeamAccessVisitor {
type Value = RepoAdminCanChangeTeamAccess;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a boolean value (true/false)")
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v {
Ok(RepoAdminCanChangeTeamAccess::Yes)
} else {
Ok(RepoAdminCanChangeTeamAccess::No)
}
}
}
impl<'de> Deserialize<'de> for RepoAdminCanChangeTeamAccess {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_bool(TeamAccessVisitor)
}
}
#[cfg(test)]
mod team_access {
use super::*;
#[test]
fn deserialize_true() {
let input = "true";
let val: RepoAdminCanChangeTeamAccess = serde_json::from_str(input).unwrap();
assert_eq!(RepoAdminCanChangeTeamAccess::Yes, val);
}
#[test]
fn deserialize_false() {
let input = "false";
let val: RepoAdminCanChangeTeamAccess = serde_json::from_str(input).unwrap();
assert_eq!(RepoAdminCanChangeTeamAccess::No, val);
}
#[test]
fn serialize_true() {
let input = RepoAdminCanChangeTeamAccess::Yes;
let val = serde_json::to_string(&input).unwrap();
assert_eq!(val.as_str(), "true")
}
#[test]
fn serialize_false() {
let input = RepoAdminCanChangeTeamAccess::No;
let val = serde_json::to_string(&input).unwrap();
assert_eq!(val.as_str(), "false")
}
}

View file

@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
use strum::{Display, EnumIs};
/// Permission level of the team
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Display, EnumIs)]
#[serde(rename_all = "lowercase")]
pub enum TeamPermission {
None,
Read,
Write,
Admin,
Owner,
}
impl Default for TeamPermission {
fn default() -> Self {
TeamPermission::None
}
}

54
src/types/misc/url.rs Normal file
View file

@ -0,0 +1,54 @@
use serde::de::{Deserializer, Error, Visitor};
use serde::{Deserialize, Serialize};
use url::Url;
/// Enum for handling optional URLs that may be empty strings in JSON
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OptionalUrl {
Some(Url),
None,
}
impl<'de> Deserialize<'de> for OptionalUrl {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct OptionalUrlVisitor;
impl<'de> Visitor<'de> for OptionalUrlVisitor {
type Value = OptionalUrl;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a URL or an empty string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
if v.is_empty() {
Ok(OptionalUrl::None)
} else {
Url::parse(v)
.map(OptionalUrl::Some)
.map_err(|_| E::custom("invalid URL"))
}
}
}
deserializer.deserialize_any(OptionalUrlVisitor)
}
}
impl Serialize for OptionalUrl {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
OptionalUrl::Some(url) => serializer.serialize_str(url.as_str()),
OptionalUrl::None => serializer.serialize_str(""),
}
}
}

View file

@ -0,0 +1,6 @@
{
"color": "#00aabb",
"description": "CreateLabelOption options for creating a label",
"exclusive": false,
"name": "example_label"
}

View file

@ -0,0 +1,9 @@
{
"description": "Sample organization",
"full_name": "SampleOrg",
"location": "Sample City, Country",
"repo_admin_change_team_access": true,
"username": "sample_org",
"visibility": "public",
"website": "https://sample.org"
}

View file

@ -0,0 +1,16 @@
{
"description": "Options when modifying a hook",
"active": true,
"authorization_header": {
"Bearer": "TOKEN"
},
"branch_filter": "main",
"config": {
"key1": "value1",
"key2": "value2"
},
"events": [
"push",
"pull_request"
]
}

View file

@ -0,0 +1,6 @@
{
"color": "#00aabb",
"description": "EditLabelOption options for updating a label",
"exclusive": false,
"name": "example_label"
}

View file

@ -0,0 +1,8 @@
{
"description": "Updated organization",
"full_name": "UpdatedOrg",
"location": "Updated City, Country",
"repo_admin_change_team_access": false,
"visibility": "private",
"website": "https://updated.org"
}

View file

@ -0,0 +1,4 @@
{
"Content-Type": "application/json",
"Authorization": "Bearer ABCDEFG123456789"
}

View file

@ -0,0 +1,18 @@
{
"active": true,
"authorization_header": {
"Bearer": "TOKEN"
},
"config": {
"url": "https://example.com/webhook",
"content_type": "json"
},
"created_at": "2023-07-27T12:34:56Z",
"events": [
"push",
"pull_request"
],
"id": 123456,
"type": "web",
"updated_at": "2023-07-27T14:45:56Z"
}

View file

@ -0,0 +1,8 @@
{
"color": "00aabb",
"description": "Label a label to an issue or a pr",
"exclusive": false,
"id": 42,
"name": "bug",
"url": "https://example.com/labels/bug"
}

View file

@ -0,0 +1,12 @@
{
"id": 77294,
"name": "---",
"full_name": "",
"avatar_url": "https://codeberg.org/avatars/9efc314b65237d5d646e1b817372afc6",
"description": "",
"website": "",
"location": "",
"visibility": "public",
"repo_admin_change_team_access": true,
"username": "---"
}

View file

@ -0,0 +1,30 @@
{
"can_create_org_repo": true,
"description": "Example team description",
"id": 12345,
"includes_all_repositories": false,
"name": "Example Team",
"organization": {
"id": 77294,
"name": "---",
"full_name": "",
"avatar_url": "https://codeberg.org/avatars/9efc314b65237d5d646e1b817372afc6",
"description": "",
"website": "",
"location": "",
"visibility": "public",
"repo_admin_change_team_access": true,
"username": "---"
},
"permission": "admin",
"units": [
"repo.code",
"repo.issues",
"repo.ext_issues"
],
"units_map": {
"repo.code": "read",
"repo.issues": "write",
"repo.ext_issues": "none"
}
}

53930
test_data/org_list.json Normal file

File diff suppressed because it is too large Load diff