1
0
Fork 0

Adding upstream version 0.5.0.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-05 05:24:00 +01:00
parent f4a13f7987
commit 36e437940f
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
11 changed files with 2513 additions and 110 deletions

View file

@ -1,6 +1,6 @@
{ {
"git": { "git": {
"sha1": "ecbc505270cabdbd34402c0c118cb35df53ff257" "sha1": "c49e0c3fc6ec4b8821e2c28dad38e1ac04571b0b"
}, },
"path_in_vcs": "" "path_in_vcs": ""
} }

View file

@ -5,11 +5,11 @@ steps:
image: rust image: rust
environment: environment:
- "FORGEJO_API_CI_INSTANCE_URL=http://forgejo-testing:3000/" - "FORGEJO_API_CI_INSTANCE_URL=http://forgejo-testing:3000/"
- FORGEJO_API_CI_TOKEN=e4f301dffd4993a3389f601761c0103291e58d85 - FORGEJO_API_CI_TOKEN=6eaba97c49d9f1bbe54f8975ea884af54826c9fe
commands: commands:
- cargo test - cargo test
services: services:
forgejo-testing: forgejo-testing:
pull: true pull: true
image: code.cartoon-aa.xyz/cyborus/ci-forgejo:8.0.0 image: code.cartoon-aa.xyz/cyborus/ci-forgejo:9.0.0

View file

@ -12,8 +12,9 @@
[package] [package]
edition = "2021" edition = "2021"
name = "forgejo-api" name = "forgejo-api"
version = "0.4.1" version = "0.5.0"
build = false build = false
autolib = false
autobins = false autobins = false
autoexamples = false autoexamples = false
autotests = false autotests = false

2
Cargo.toml.orig generated
View file

@ -1,7 +1,7 @@
workspace = { members = ["generator"] } workspace = { members = ["generator"] }
[package] [package]
name = "forgejo-api" name = "forgejo-api"
version = "0.4.1" version = "0.5.0"
edition = "2021" edition = "2021"
license = "Apache-2.0 OR MIT" license = "Apache-2.0 OR MIT"
repository = "https://codeberg.org/Cyborus/forgejo-api" repository = "https://codeberg.org/Cyborus/forgejo-api"

View file

@ -3,6 +3,26 @@ use crate::ForgejoError;
use std::collections::BTreeMap; use std::collections::BTreeMap;
impl crate::Forgejo { impl crate::Forgejo {
/// Returns the instance's Actor
pub async fn activitypub_instance_actor(&self) -> Result<ActivityPub, ForgejoError> {
let request = self.get("activitypub/actor").build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Send to the inbox
pub async fn activitypub_instance_actor_inbox(&self) -> Result<(), ForgejoError> {
let request = self.post("activitypub/actor/inbox").build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
204 => Ok(()),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Returns the Repository actor for a repo /// Returns the Repository actor for a repo
/// ///
/// - `repository-id`: repository ID of the repo /// - `repository-id`: repository ID of the repo
@ -206,6 +226,239 @@ impl crate::Forgejo {
} }
} }
/// List the available quota groups
pub async fn admin_list_quota_groups(&self) -> Result<Vec<QuotaGroup>, ForgejoError> {
let request = self.get("admin/quota/groups").build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Create a new quota group
///
/// - `group`: Definition of the quota group
/// See [`CreateQuotaGroupOptions`]
pub async fn admin_create_quota_group(
&self,
group: CreateQuotaGroupOptions,
) -> Result<QuotaGroup, ForgejoError> {
let request = self.post("admin/quota/groups").json(&group).build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
201 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Get information about the quota group
///
/// - `quotagroup`: quota group to query
pub async fn admin_get_quota_group(
&self,
quotagroup: &str,
) -> Result<QuotaGroup, ForgejoError> {
let request = self
.get(&format!("admin/quota/groups/{quotagroup}"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Delete a quota group
///
/// - `quotagroup`: quota group to delete
pub async fn admin_delete_quota_group(&self, quotagroup: &str) -> Result<(), ForgejoError> {
let request = self
.delete(&format!("admin/quota/groups/{quotagroup}"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
204 => Ok(()),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Adds a rule to a quota group
///
/// - `quotagroup`: quota group to add a rule to
/// - `quotarule`: the name of the quota rule to add to the group
pub async fn admin_add_rule_to_quota_group(
&self,
quotagroup: &str,
quotarule: &str,
) -> Result<(), ForgejoError> {
let request = self
.put(&format!(
"admin/quota/groups/{quotagroup}/rules/{quotarule}"
))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
204 => Ok(()),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Removes a rule from a quota group
///
/// - `quotagroup`: quota group to remove a rule from
/// - `quotarule`: the name of the quota rule to remove from the group
pub async fn admin_remove_rule_from_quota_group(
&self,
quotagroup: &str,
quotarule: &str,
) -> Result<(), ForgejoError> {
let request = self
.delete(&format!(
"admin/quota/groups/{quotagroup}/rules/{quotarule}"
))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
204 => Ok(()),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// List users in a quota group
///
/// - `quotagroup`: quota group to list members of
pub async fn admin_list_users_in_quota_group(
&self,
quotagroup: &str,
) -> Result<Vec<User>, ForgejoError> {
let request = self
.get(&format!("admin/quota/groups/{quotagroup}/users"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Add a user to a quota group
///
/// - `quotagroup`: quota group to add the user to
/// - `username`: username of the user to add to the quota group
pub async fn admin_add_user_to_quota_group(
&self,
quotagroup: &str,
username: &str,
) -> Result<(), ForgejoError> {
let request = self
.put(&format!("admin/quota/groups/{quotagroup}/users/{username}"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
204 => Ok(()),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Remove a user from a quota group
///
/// - `quotagroup`: quota group to remove a user from
/// - `username`: username of the user to remove from the quota group
pub async fn admin_remove_user_from_quota_group(
&self,
quotagroup: &str,
username: &str,
) -> Result<(), ForgejoError> {
let request = self
.delete(&format!("admin/quota/groups/{quotagroup}/users/{username}"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
204 => Ok(()),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// List the available quota rules
pub async fn admin_list_quota_rules(&self) -> Result<Vec<QuotaRuleInfo>, ForgejoError> {
let request = self.get("admin/quota/rules").build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Create a new quota rule
///
/// - `rule`: Definition of the quota rule
/// See [`CreateQuotaRuleOptions`]
pub async fn admin_create_quota_rule(
&self,
rule: CreateQuotaRuleOptions,
) -> Result<QuotaRuleInfo, ForgejoError> {
let request = self.post("admin/quota/rules").json(&rule).build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
201 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Get information about a quota rule
///
/// - `quotarule`: quota rule to query
pub async fn admin_get_quota_rule(
&self,
quotarule: &str,
) -> Result<QuotaRuleInfo, ForgejoError> {
let request = self
.get(&format!("admin/quota/rules/{quotarule}"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Deletes a quota rule
///
/// - `quotarule`: quota rule to delete
pub async fn admin_delete_quota_rule(&self, quotarule: &str) -> Result<(), ForgejoError> {
let request = self
.delete(&format!("admin/quota/rules/{quotarule}"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
204 => Ok(()),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Change an existing quota rule
///
/// - `quotarule`: Quota rule to change
/// - `rule`: See [`EditQuotaRuleOptions`]
pub async fn admin_edit_quota_rule(
&self,
quotarule: &str,
rule: EditQuotaRuleOptions,
) -> Result<QuotaRuleInfo, ForgejoError> {
let request = self
.patch(&format!("admin/quota/rules/{quotarule}"))
.json(&rule)
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Get an global actions runner registration token /// Get an global actions runner registration token
pub async fn admin_get_runner_registration_token( pub async fn admin_get_runner_registration_token(
&self, &self,
@ -393,6 +646,40 @@ impl crate::Forgejo {
} }
} }
/// Get the user's quota info
///
/// - `username`: username of user to query
pub async fn admin_get_user_quota(&self, username: &str) -> Result<QuotaInfo, ForgejoError> {
let request = self.get(&format!("admin/users/{username}/quota")).build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Set the user's quota groups to a given list.
///
/// - `username`: username of the user to modify the quota groups from
/// - `groups`: list of groups that the user should be a member of
/// See [`SetUserQuotaGroupsOptions`]
pub async fn admin_set_user_quota_groups(
&self,
username: &str,
groups: SetUserQuotaGroupsOptions,
) -> Result<(), ForgejoError> {
let request = self
.post(&format!("admin/users/{username}/quota/groups"))
.json(&groups)
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
204 => Ok(()),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Rename a user /// Rename a user
/// ///
/// - `username`: existing username of user /// - `username`: existing username of user
@ -1256,6 +1543,84 @@ impl crate::Forgejo {
} }
} }
/// Get quota information for an organization
///
/// - `org`: name of the organization
pub async fn org_get_quota(&self, org: &str) -> Result<QuotaInfo, ForgejoError> {
let request = self.get(&format!("orgs/{org}/quota")).build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// List the artifacts affecting the organization's quota
///
/// - `org`: name of the organization
pub async fn org_list_quota_artifacts(
&self,
org: &str,
query: OrgListQuotaArtifactsQuery,
) -> Result<Vec<QuotaUsedArtifact>, ForgejoError> {
let request = self
.get(&format!("orgs/{org}/quota/artifacts?{query}"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// List the attachments affecting the organization's quota
///
/// - `org`: name of the organization
pub async fn org_list_quota_attachments(
&self,
org: &str,
query: OrgListQuotaAttachmentsQuery,
) -> Result<Vec<QuotaUsedAttachment>, ForgejoError> {
let request = self
.get(&format!("orgs/{org}/quota/attachments?{query}"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Check if the organization is over quota for a given subject
///
/// - `org`: name of the organization
pub async fn org_check_quota(&self, org: &str) -> Result<(), ForgejoError> {
let request = self.get(&format!("orgs/{org}/quota/check")).build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(()),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// List the packages affecting the organization's quota
///
/// - `org`: name of the organization
pub async fn org_list_quota_packages(
&self,
org: &str,
query: OrgListQuotaPackagesQuery,
) -> Result<Vec<QuotaUsedPackage>, ForgejoError> {
let request = self
.get(&format!("orgs/{org}/quota/packages?{query}"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// List an organization's repos /// List an organization's repos
/// ///
/// - `org`: name of the organization /// - `org`: name of the organization
@ -1533,6 +1898,27 @@ impl crate::Forgejo {
} }
} }
/// Get a repository's actions runner registration token
///
/// - `owner`: owner of the repo
/// - `repo`: name of the repo
pub async fn repo_get_runner_registration_token(
&self,
owner: &str,
repo: &str,
) -> Result<RegistrationTokenHeaders, ForgejoError> {
let request = self
.get(&format!(
"repos/{owner}/{repo}/actions/runners/registration-token"
))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.headers().try_into()?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// List an repo's actions secrets /// List an repo's actions secrets
/// ///
/// - `owner`: owner of the repository /// - `owner`: owner of the repository
@ -3235,11 +3621,10 @@ impl crate::Forgejo {
attachment: Vec<u8>, attachment: Vec<u8>,
query: IssueCreateIssueCommentAttachmentQuery, query: IssueCreateIssueCommentAttachmentQuery,
) -> Result<Attachment, ForgejoError> { ) -> Result<Attachment, ForgejoError> {
let request = self let builder = self.post(&format!(
.post(&format!(
"repos/{owner}/{repo}/issues/comments/{id}/assets?{query}" "repos/{owner}/{repo}/issues/comments/{id}/assets?{query}"
)) ));
.multipart( let builder = builder.multipart(
reqwest::multipart::Form::new().part( reqwest::multipart::Form::new().part(
"attachment", "attachment",
reqwest::multipart::Part::bytes(attachment) reqwest::multipart::Part::bytes(attachment)
@ -3247,8 +3632,8 @@ impl crate::Forgejo {
.mime_str("*/*") .mime_str("*/*")
.unwrap(), .unwrap(),
), ),
) );
.build()?; let request = builder.build()?;
let response = self.execute(request).await?; let response = self.execute(request).await?;
match response.status().as_u16() { match response.status().as_u16() {
201 => Ok(response.json().await?), 201 => Ok(response.json().await?),
@ -3530,11 +3915,10 @@ impl crate::Forgejo {
attachment: Vec<u8>, attachment: Vec<u8>,
query: IssueCreateIssueAttachmentQuery, query: IssueCreateIssueAttachmentQuery,
) -> Result<Attachment, ForgejoError> { ) -> Result<Attachment, ForgejoError> {
let request = self let builder = self.post(&format!(
.post(&format!(
"repos/{owner}/{repo}/issues/{index}/assets?{query}" "repos/{owner}/{repo}/issues/{index}/assets?{query}"
)) ));
.multipart( let builder = builder.multipart(
reqwest::multipart::Form::new().part( reqwest::multipart::Form::new().part(
"attachment", "attachment",
reqwest::multipart::Part::bytes(attachment) reqwest::multipart::Part::bytes(attachment)
@ -3542,8 +3926,8 @@ impl crate::Forgejo {
.mime_str("*/*") .mime_str("*/*")
.unwrap(), .unwrap(),
), ),
) );
.build()?; let request = builder.build()?;
let response = self.execute(request).await?; let response = self.execute(request).await?;
match response.status().as_u16() { match response.status().as_u16() {
201 => Ok(response.json().await?), 201 => Ok(response.json().await?),
@ -5791,20 +6175,22 @@ impl crate::Forgejo {
/// - `owner`: owner of the repo /// - `owner`: owner of the repo
/// - `repo`: name of the repo /// - `repo`: name of the repo
/// - `id`: id of the release /// - `id`: id of the release
/// - `attachment`: attachment to upload /// - `attachment`: attachment to upload (this parameter is incompatible with `external_url`)
/// - `external_url`: url to external asset (this parameter is incompatible with `attachment`)
pub async fn repo_create_release_attachment( pub async fn repo_create_release_attachment(
&self, &self,
owner: &str, owner: &str,
repo: &str, repo: &str,
id: u64, id: u64,
attachment: Vec<u8>, attachment: Option<Vec<u8>>,
external_url: Option<Vec<u8>>,
query: RepoCreateReleaseAttachmentQuery, query: RepoCreateReleaseAttachmentQuery,
) -> Result<Attachment, ForgejoError> { ) -> Result<Attachment, ForgejoError> {
let request = self let builder = self.post(&format!(
.post(&format!(
"repos/{owner}/{repo}/releases/{id}/assets?{query}" "repos/{owner}/{repo}/releases/{id}/assets?{query}"
)) ));
.multipart( let builder = match attachment {
Some(attachment) => builder.multipart(
reqwest::multipart::Form::new().part( reqwest::multipart::Form::new().part(
"attachment", "attachment",
reqwest::multipart::Part::bytes(attachment) reqwest::multipart::Part::bytes(attachment)
@ -5812,8 +6198,22 @@ impl crate::Forgejo {
.mime_str("*/*") .mime_str("*/*")
.unwrap(), .unwrap(),
), ),
) ),
.build()?; None => builder,
};
let builder = match external_url {
Some(external_url) => builder.multipart(
reqwest::multipart::Form::new().part(
"attachment",
reqwest::multipart::Part::bytes(external_url)
.file_name("file")
.mime_str("*/*")
.unwrap(),
),
),
None => builder,
};
let request = builder.build()?;
let response = self.execute(request).await?; let response = self.execute(request).await?;
match response.status().as_u16() { match response.status().as_u16() {
201 => Ok(response.json().await?), 201 => Ok(response.json().await?),
@ -5918,25 +6318,6 @@ impl crate::Forgejo {
} }
} }
/// Get a repository's actions runner registration token
///
/// - `owner`: owner of the repo
/// - `repo`: name of the repo
pub async fn repo_get_runner_registration_token(
&self,
owner: &str,
repo: &str,
) -> Result<RegistrationTokenHeaders, ForgejoError> {
let request = self
.get(&format!("repos/{owner}/{repo}/runners/registration-token"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.headers().try_into()?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Get signing-key.gpg for given repository /// Get signing-key.gpg for given repository
/// ///
/// - `owner`: owner of the repo /// - `owner`: owner of the repo
@ -7583,6 +7964,70 @@ impl crate::Forgejo {
} }
} }
/// Get quota information for the authenticated user
pub async fn user_get_quota(&self) -> Result<QuotaInfo, ForgejoError> {
let request = self.get("user/quota").build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// List the artifacts affecting the authenticated user's quota
///
pub async fn user_list_quota_artifacts(
&self,
query: UserListQuotaArtifactsQuery,
) -> Result<Vec<QuotaUsedArtifact>, ForgejoError> {
let request = self.get(&format!("user/quota/artifacts?{query}")).build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// List the attachments affecting the authenticated user's quota
///
pub async fn user_list_quota_attachments(
&self,
query: UserListQuotaAttachmentsQuery,
) -> Result<Vec<QuotaUsedAttachment>, ForgejoError> {
let request = self
.get(&format!("user/quota/attachments?{query}"))
.build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// Check if the authenticated user is over quota for a given subject
pub async fn user_check_quota(&self) -> Result<(), ForgejoError> {
let request = self.get("user/quota/check").build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(()),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// List the packages affecting the authenticated user's quota
///
pub async fn user_list_quota_packages(
&self,
query: UserListQuotaPackagesQuery,
) -> Result<Vec<QuotaUsedPackage>, ForgejoError> {
let request = self.get(&format!("user/quota/packages?{query}")).build()?;
let response = self.execute(request).await?;
match response.status().as_u16() {
200 => Ok(response.json().await?),
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
}
}
/// List the repos that the authenticated user owns /// List the repos that the authenticated user owns
/// ///
pub async fn user_current_list_repos( pub async fn user_current_list_repos(

View file

@ -147,9 +147,18 @@ pub struct ActivityPub {
/// AddCollaboratorOption options when adding a user as a collaborator of a repository /// AddCollaboratorOption options when adding a user as a collaborator of a repository
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct AddCollaboratorOption { pub struct AddCollaboratorOption {
pub permission: Option<String>, pub permission: Option<AddCollaboratorOptionPermission>,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum AddCollaboratorOptionPermission {
#[serde(rename = "read")]
Read,
#[serde(rename = "write")]
Write,
#[serde(rename = "admin")]
Admin,
}
/// AddTimeOption options for adding time to an issue /// AddTimeOption options for adding time to an issue
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct AddTimeOption { pub struct AddTimeOption {
@ -196,9 +205,18 @@ pub struct Attachment {
pub id: Option<i64>, pub id: Option<i64>,
pub name: Option<String>, pub name: Option<String>,
pub size: Option<i64>, pub size: Option<i64>,
#[serde(rename = "type")]
pub r#type: Option<AttachmentType>,
pub uuid: Option<String>, pub uuid: Option<String>,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum AttachmentType {
#[serde(rename = "attachment")]
Attachment,
#[serde(rename = "external")]
External,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct BlockedUser { pub struct BlockedUser {
pub block_id: Option<i64>, pub block_id: Option<i64>,
@ -767,8 +785,62 @@ pub struct CreatePushMirrorOption {
pub remote_password: Option<String>, pub remote_password: Option<String>,
pub remote_username: Option<String>, pub remote_username: Option<String>,
pub sync_on_commit: Option<bool>, pub sync_on_commit: Option<bool>,
pub use_ssh: Option<bool>,
} }
/// CreateQutaGroupOptions represents the options for creating a quota group
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CreateQuotaGroupOptions {
/// Name of the quota group to create
pub name: Option<String>,
/// Rules to add to the newly created group.
///
/// If a rule does not exist, it will be created.
pub rules: Option<Vec<CreateQuotaRuleOptions>>,
}
/// CreateQuotaRuleOptions represents the options for creating a quota rule
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CreateQuotaRuleOptions {
/// The limit set by the rule
pub limit: Option<i64>,
/// Name of the rule to create
pub name: Option<String>,
/// The subjects affected by the rule
pub subjects: Option<Vec<CreateQuotaRuleOptionsSubjects>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum CreateQuotaRuleOptionsSubjects {
#[serde(rename = "none")]
None,
#[serde(rename = "size:all")]
SizeAll,
#[serde(rename = "size:repos:all")]
SizeReposAll,
#[serde(rename = "size:repos:public")]
SizeReposPublic,
#[serde(rename = "size:repos:private")]
SizeReposPrivate,
#[serde(rename = "size:git:all")]
SizeGitAll,
#[serde(rename = "size:git:lfs")]
SizeGitLfs,
#[serde(rename = "size:assets:all")]
SizeAssetsAll,
#[serde(rename = "size:assets:attachments:all")]
SizeAssetsAttachmentsAll,
#[serde(rename = "size:assets:attachments:issues")]
SizeAssetsAttachmentsIssues,
#[serde(rename = "size:assets:attachments:releases")]
SizeAssetsAttachmentsReleases,
#[serde(rename = "size:assets:artifacts")]
SizeAssetsArtifacts,
#[serde(rename = "size:assets:packages:all")]
SizeAssetsPackagesAll,
#[serde(rename = "size:assets:wiki")]
SizeAssetsWiki,
}
/// CreateReleaseOption options when creating a release /// CreateReleaseOption options when creating a release
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CreateReleaseOption { pub struct CreateReleaseOption {
@ -1004,6 +1076,9 @@ pub struct DispatchWorkflowOption {
/// EditAttachmentOptions options for editing attachments /// EditAttachmentOptions options for editing attachments
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct EditAttachmentOptions { pub struct EditAttachmentOptions {
#[serde(deserialize_with = "crate::none_if_blank_url")]
/// (Can only be set if existing attachment is of external type)
pub browser_download_url: Option<url::Url>,
pub name: Option<String>, pub name: Option<String>,
} }
@ -1146,6 +1221,15 @@ pub struct EditPullRequestOption {
pub unset_due_date: Option<bool>, pub unset_due_date: Option<bool>,
} }
/// EditQuotaRuleOptions represents the options for editing a quota rule
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct EditQuotaRuleOptions {
/// The limit set by the rule
pub limit: Option<i64>,
/// The subjects affected by the rule
pub subjects: Option<Vec<String>>,
}
/// EditReactionOption contain the reaction type /// EditReactionOption contain the reaction type
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct EditReactionOption { pub struct EditReactionOption {
@ -2192,6 +2276,7 @@ pub struct PullRequest {
pub pin_order: Option<i64>, pub pin_order: Option<i64>,
#[serde(deserialize_with = "crate::requested_reviewers_ignore_null")] #[serde(deserialize_with = "crate::requested_reviewers_ignore_null")]
pub requested_reviewers: Option<Vec<User>>, pub requested_reviewers: Option<Vec<User>>,
pub requested_reviewers_teams: Option<Vec<Team>>,
/// number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR) /// number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
pub review_comments: Option<i64>, pub review_comments: Option<i64>,
pub state: Option<StateType>, pub state: Option<StateType>,
@ -2277,12 +2362,150 @@ pub struct PushMirror {
pub last_error: Option<String>, pub last_error: Option<String>,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
pub last_update: Option<time::OffsetDateTime>, pub last_update: Option<time::OffsetDateTime>,
pub public_key: Option<String>,
pub remote_address: Option<String>, pub remote_address: Option<String>,
pub remote_name: Option<String>, pub remote_name: Option<String>,
pub repo_name: Option<String>, pub repo_name: Option<String>,
pub sync_on_commit: Option<bool>, pub sync_on_commit: Option<bool>,
} }
/// QuotaGroup represents a quota group
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaGroup {
/// Name of the group
pub name: Option<String>,
/// Rules associated with the group
pub rules: Option<Vec<QuotaRuleInfo>>,
}
/// QuotaInfo represents information about a user's quota
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaInfo {
pub groups: Option<Vec<QuotaGroup>>,
pub used: Option<QuotaUsed>,
}
/// QuotaRuleInfo contains information about a quota rule
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaRuleInfo {
/// The limit set by the rule
pub limit: Option<i64>,
/// Name of the rule (only shown to admins)
pub name: Option<String>,
/// Subjects the rule affects
pub subjects: Option<Vec<String>>,
}
/// QuotaUsed represents the quota usage of a user
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaUsed {
pub size: Option<QuotaUsedSize>,
}
/// QuotaUsedArtifact represents an artifact counting towards a user's quota
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaUsedArtifact {
#[serde(deserialize_with = "crate::none_if_blank_url")]
/// HTML URL to the action run containing the artifact
pub html_url: Option<url::Url>,
/// Name of the artifact
pub name: Option<String>,
/// Size of the artifact (compressed)
pub size: Option<i64>,
}
/// QuotaUsedAttachment represents an attachment counting towards a user's quota
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaUsedAttachment {
#[serde(deserialize_with = "crate::none_if_blank_url")]
/// API URL for the attachment
pub api_url: Option<url::Url>,
/// Context for the attachment: URLs to the containing object
pub contained_in: Option<QuotaUsedAttachmentContainedIn>,
/// Filename of the attachment
pub name: Option<String>,
/// Size of the attachment (in bytes)
pub size: Option<i64>,
}
/// Context for the attachment: URLs to the containing object
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaUsedAttachmentContainedIn {
#[serde(deserialize_with = "crate::none_if_blank_url")]
/// API URL for the object that contains this attachment
pub api_url: Option<url::Url>,
#[serde(deserialize_with = "crate::none_if_blank_url")]
/// HTML URL for the object that contains this attachment
pub html_url: Option<url::Url>,
}
/// QuotaUsedPackage represents a package counting towards a user's quota
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaUsedPackage {
#[serde(deserialize_with = "crate::none_if_blank_url")]
/// HTML URL to the package version
pub html_url: Option<url::Url>,
/// Name of the package
pub name: Option<String>,
/// Size of the package version
pub size: Option<i64>,
/// Type of the package
#[serde(rename = "type")]
pub r#type: Option<String>,
/// Version of the package
pub version: Option<String>,
}
/// QuotaUsedSize represents the size-based quota usage of a user
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaUsedSize {
pub assets: Option<QuotaUsedSizeAssets>,
pub git: Option<QuotaUsedSizeGit>,
pub repos: Option<QuotaUsedSizeRepos>,
}
/// QuotaUsedSizeAssets represents the size-based asset usage of a user
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaUsedSizeAssets {
/// Storage size used for the user's artifacts
pub artifacts: Option<i64>,
pub attachments: Option<QuotaUsedSizeAssetsAttachments>,
pub packages: Option<QuotaUsedSizeAssetsPackages>,
}
/// QuotaUsedSizeAssetsAttachments represents the size-based attachment quota usage of a user
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaUsedSizeAssetsAttachments {
/// Storage size used for the user's issue & comment attachments
pub issues: Option<i64>,
/// Storage size used for the user's release attachments
pub releases: Option<i64>,
}
/// QuotaUsedSizeAssetsPackages represents the size-based package quota usage of a user
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaUsedSizeAssetsPackages {
/// Storage suze used for the user's packages
pub all: Option<i64>,
}
/// QuotaUsedSizeGit represents the size-based git (lfs) quota usage of a user
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaUsedSizeGit {
/// Storage size of the user's Git LFS objects
#[serde(rename = "LFS")]
pub lfs: Option<i64>,
}
/// QuotaUsedSizeRepos represents the size-based repository quota usage of a user
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotaUsedSizeRepos {
/// Storage size of the user's private repositories
pub private: Option<i64>,
/// Storage size of the user's public repositories
pub public: Option<i64>,
}
/// Reaction contain one reaction /// Reaction contain one reaction
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Reaction { pub struct Reaction {
@ -2491,6 +2714,13 @@ pub struct ServerVersion {
pub version: Option<String>, pub version: Option<String>,
} }
/// SetUserQuotaGroupsOptions represents the quota groups of a user
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SetUserQuotaGroupsOptions {
/// Quota groups the user shall have
pub groups: Vec<String>,
}
/// StateType issue state type /// StateType issue state type
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
@ -3091,6 +3321,46 @@ impl TryFrom<&reqwest::header::HeaderMap> for InvalidTopicsErrorHeaders {
} }
} }
pub struct QuotaExceededHeaders {
pub message: Option<String>,
pub user_id: Option<i64>,
pub username: Option<String>,
}
impl TryFrom<&reqwest::header::HeaderMap> for QuotaExceededHeaders {
type Error = StructureError;
fn try_from(map: &reqwest::header::HeaderMap) -> Result<Self, Self::Error> {
let message = map
.get("message")
.map(|s| -> Result<_, _> {
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
Ok(s.to_string())
})
.transpose()?;
let user_id = map
.get("user_id")
.map(|s| -> Result<_, _> {
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
s.parse::<i64>()
.map_err(|_| StructureError::HeaderParseFailed)
})
.transpose()?;
let username = map
.get("username")
.map(|s| -> Result<_, _> {
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
Ok(s.to_string())
})
.transpose()?;
Ok(Self {
message,
user_id,
username,
})
}
}
pub struct RepoArchivedErrorHeaders { pub struct RepoArchivedErrorHeaders {
pub message: Option<String>, pub message: Option<String>,
pub url: Option<url::Url>, pub url: Option<url::Url>,
@ -3681,6 +3951,69 @@ impl std::fmt::Display for OrgListPublicMembersQuery {
} }
} }
#[derive(Debug, Clone, PartialEq, Default)]
pub struct OrgListQuotaArtifactsQuery {
/// page number of results to return (1-based)
pub page: Option<u32>,
/// page size of results
pub limit: Option<u32>,
}
impl std::fmt::Display for OrgListQuotaArtifactsQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(page) = &self.page {
write!(f, "page={page}&")?;
}
if let Some(limit) = &self.limit {
write!(f, "limit={limit}&")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct OrgListQuotaAttachmentsQuery {
/// page number of results to return (1-based)
pub page: Option<u32>,
/// page size of results
pub limit: Option<u32>,
}
impl std::fmt::Display for OrgListQuotaAttachmentsQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(page) = &self.page {
write!(f, "page={page}&")?;
}
if let Some(limit) = &self.limit {
write!(f, "limit={limit}&")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct OrgListQuotaPackagesQuery {
/// page number of results to return (1-based)
pub page: Option<u32>,
/// page size of results
pub limit: Option<u32>,
}
impl std::fmt::Display for OrgListQuotaPackagesQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(page) = &self.page {
write!(f, "page={page}&")?;
}
if let Some(limit) = &self.limit {
write!(f, "limit={limit}&")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
pub struct OrgListReposQuery { pub struct OrgListReposQuery {
/// page number of results to return (1-based) /// page number of results to return (1-based)
@ -6071,12 +6404,77 @@ impl std::fmt::Display for OrgListCurrentUserOrgsQuery {
} }
} }
#[derive(Debug, Clone, PartialEq, Default)]
pub struct UserListQuotaArtifactsQuery {
/// page number of results to return (1-based)
pub page: Option<u32>,
/// page size of results
pub limit: Option<u32>,
}
impl std::fmt::Display for UserListQuotaArtifactsQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(page) = &self.page {
write!(f, "page={page}&")?;
}
if let Some(limit) = &self.limit {
write!(f, "limit={limit}&")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct UserListQuotaAttachmentsQuery {
/// page number of results to return (1-based)
pub page: Option<u32>,
/// page size of results
pub limit: Option<u32>,
}
impl std::fmt::Display for UserListQuotaAttachmentsQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(page) = &self.page {
write!(f, "page={page}&")?;
}
if let Some(limit) = &self.limit {
write!(f, "limit={limit}&")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct UserListQuotaPackagesQuery {
/// page number of results to return (1-based)
pub page: Option<u32>,
/// page size of results
pub limit: Option<u32>,
}
impl std::fmt::Display for UserListQuotaPackagesQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(page) = &self.page {
write!(f, "page={page}&")?;
}
if let Some(limit) = &self.limit {
write!(f, "limit={limit}&")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
pub struct UserCurrentListReposQuery { pub struct UserCurrentListReposQuery {
/// page number of results to return (1-based) /// page number of results to return (1-based)
pub page: Option<u32>, pub page: Option<u32>,
/// page size of results /// page size of results
pub limit: Option<u32>, pub limit: Option<u32>,
/// order the repositories by name (default), id, or size
pub order_by: Option<String>,
} }
impl std::fmt::Display for UserCurrentListReposQuery { impl std::fmt::Display for UserCurrentListReposQuery {
@ -6087,6 +6485,9 @@ impl std::fmt::Display for UserCurrentListReposQuery {
if let Some(limit) = &self.limit { if let Some(limit) = &self.limit {
write!(f, "limit={limit}&")?; write!(f, "limit={limit}&")?;
} }
if let Some(order_by) = &self.order_by {
write!(f, "order_by={order_by}&")?;
}
Ok(()) Ok(())
} }

View file

@ -367,7 +367,7 @@ where
{ {
let list: Option<Vec<Option<structs::User>>> = let list: Option<Vec<Option<structs::User>>> =
Option::deserialize(deserializer).map_err(DE::custom)?; Option::deserialize(deserializer).map_err(DE::custom)?;
Ok(list.map(|list| list.into_iter().filter_map(|x| x).collect::<Vec<_>>())) Ok(list.map(|list| list.into_iter().flatten().collect::<Vec<_>>()))
} }
fn parse_ssh_url(raw_url: &String) -> Result<Url, url::ParseError> { fn parse_ssh_url(raw_url: &String) -> Result<Url, url::ParseError> {

File diff suppressed because it is too large Load diff

View file

@ -30,10 +30,7 @@ async fn user() {
.await .await
.expect("failed to search users"); .expect("failed to search users");
assert!( assert!(
users users.iter().any(|u| u.login.as_ref().unwrap() == "Pipis"),
.iter()
.find(|u| u.login.as_ref().unwrap() == "Pipis")
.is_some(),
"could not find new user" "could not find new user"
); );
let query = AdminGetAllEmailsQuery::default(); let query = AdminGetAllEmailsQuery::default();
@ -44,8 +41,7 @@ async fn user() {
assert!( assert!(
users users
.iter() .iter()
.find(|u| u.email.as_ref().unwrap() == "pipis@noreply.example.org") .any(|u| u.email.as_ref().unwrap() == "pipis@noreply.example.org"),
.is_some(),
"could not find new user" "could not find new user"
); );
} }
@ -153,7 +149,7 @@ async fn cron() {
.admin_cron_list(query) .admin_cron_list(query)
.await .await
.expect("failed to get crons list"); .expect("failed to get crons list");
api.admin_cron_run(&crons.get(0).expect("no crons").name.as_ref().unwrap()) api.admin_cron_run(crons.first().expect("no crons").name.as_ref().unwrap())
.await .await
.expect("failed to run cron"); .expect("failed to run cron");
} }
@ -192,3 +188,91 @@ async fn hook() {
.await .await
.expect("failed to delete hook"); .expect("failed to delete hook");
} }
#[tokio::test]
async fn quota_group() {
let api = common::login();
let user_opts = CreateUserOption {
created_at: None,
email: "1997@example.com".into(),
full_name: None,
login_name: None,
must_change_password: None,
password: Some("dialtone".into()),
restricted: None,
send_notify: None,
source_id: None,
username: "salesman".into(),
visibility: None,
};
api.admin_create_user(user_opts)
.await
.expect("failed to create user");
let group = CreateQuotaGroupOptions {
name: Some("no doing anything".into()),
rules: Some(vec![CreateQuotaRuleOptions {
limit: Some(0),
name: Some("blah".into()),
subjects: Some(vec![CreateQuotaRuleOptionsSubjects::SizeAll]),
}]),
};
let quota_group = api
.admin_create_quota_group(group)
.await
.expect("failed to create quota group");
api.admin_add_user_to_quota_group("no doing anything", "salesman")
.await
.expect("failed to add user to quota group");
assert!(quota_group
.name
.as_ref()
.is_some_and(|name| name == "no doing anything"));
assert!(quota_group
.rules
.as_ref()
.is_some_and(|rules| rules.len() == 1));
let quota_groups = api
.admin_list_quota_groups()
.await
.expect("failed to list quota groups");
assert_eq!(quota_groups.len(), 1);
assert_eq!(&quota_groups[0], &quota_group);
let quota_info = api
.admin_get_user_quota("salesman")
.await
.expect("failed to get user quota");
let usage = quota_info
.used
.expect("quota info missing usage info")
.size
.expect("quota info missing size info");
assert!(usage
.git
.is_some_and(|git| git.lfs.is_some_and(|lfs| lfs == 0)));
assert!(usage
.repos
.as_ref()
.is_some_and(|repos| repos.public.is_some_and(|lfs| lfs == 0)));
assert!(usage
.repos
.is_some_and(|repos| repos.private.is_some_and(|lfs| lfs == 0)));
assert!(usage
.assets
.is_some_and(|assets| assets.artifacts.is_some_and(|lfs| lfs == 0)));
api.admin_remove_rule_from_quota_group("no doing anything", "blah")
.await
.expect("failed to delete rule from quota group");
api.admin_remove_user_from_quota_group("no doing anything", "salesman")
.await
.expect("failed to remove user from quota group");
api.admin_delete_quota_group("no doing anything")
.await
.expect("failed to delete quota group");
}

View file

@ -271,7 +271,8 @@ async fn release() {
"TestingAdmin", "TestingAdmin",
"release-test", "release-test",
release.id.unwrap() as u64, release.id.unwrap() as u64,
b"This is a file!".to_vec(), Some(b"This is a file!".to_vec()),
None,
RepoCreateReleaseAttachmentQuery { RepoCreateReleaseAttachmentQuery {
name: Some("test.txt".into()), name: Some("test.txt".into()),
}, },
@ -390,7 +391,7 @@ async fn team_pr_review_request() {
.repo_get_pull_request("team-review-org", "team-pr-review", 1) .repo_get_pull_request("team-review-org", "team-pr-review", 1)
.await .await
.expect("couldn't get pr"); .expect("couldn't get pr");
assert_eq!(pr.requested_reviewers, Some(Vec::new())); assert_eq!(pr.requested_reviewers, None);
} }
#[tokio::test] #[tokio::test]

View file

@ -164,7 +164,7 @@ async fn oauth2_login() {
let code = code.unwrap(); let code = code.unwrap();
// Redeem the code and check it works // Redeem the code and check it works
let url = url::Url::parse(&base_url).unwrap(); let url = url::Url::parse(base_url).unwrap();
let api = forgejo_api::Forgejo::new(forgejo_api::Auth::None, url.clone()).unwrap(); let api = forgejo_api::Forgejo::new(forgejo_api::Auth::None, url.clone()).unwrap();
let request = forgejo_api::structs::OAuthTokenRequest::Confidential { let request = forgejo_api::structs::OAuthTokenRequest::Confidential {