summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--generator/src/structs.rs17
-rw-r--r--src/generated/structs.rs3
-rw-r--r--src/lib.rs36
-rw-r--r--tests/ci_test.rs6
-rw-r--r--tests/repo_data.json91
5 files changed, 148 insertions, 5 deletions
diff --git a/generator/src/structs.rs b/generator/src/structs.rs
index 3449f5f..4c78fac 100644
--- a/generator/src/structs.rs
+++ b/generator/src/structs.rs
@@ -63,7 +63,7 @@ pub fn create_struct_for_definition(
crate::schema_subtype_name(spec, name, prop_name, value, &mut field_ty)?;
crate::schema_subtypes(spec, name, prop_name, value, &mut subtypes)?;
}
- if field_name.ends_with("url") && field_name != "ssh_url" && field_ty == "String" {
+ if field_name.ends_with("url") && field_ty == "String" {
field_ty = "url::Url".into()
}
if field_ty == name {
@@ -73,7 +73,16 @@ pub fn create_struct_for_definition(
field_ty = format!("Option<{field_ty}>")
}
if field_ty == "Option<url::Url>" {
- fields.push_str("#[serde(deserialize_with = \"crate::none_if_blank_url\")]\n");
+ if field_name == "ssh_url" {
+ fields.push_str(
+ "#[serde(deserialize_with = \"crate::deserialize_optional_ssh_url\")]\n",
+ );
+ } else {
+ fields.push_str("#[serde(deserialize_with = \"crate::none_if_blank_url\")]\n");
+ }
+ }
+ if field_ty == "url::Url" && field_name == "ssh_url" {
+ fields.push_str("#[serde(deserialize_with = \"crate::deserialize_ssh_url\")]\n");
}
if field_ty == "time::OffsetDateTime" {
fields.push_str("#[serde(with = \"time::serde::rfc3339\")]\n");
@@ -106,8 +115,8 @@ pub fn create_struct_for_definition(
}
}
- if let Some(additonal_schema) = &schema.additional_properties {
- let prop_ty = crate::schema_ref_type_name(spec, additonal_schema)?;
+ if let Some(additional_schema) = &schema.additional_properties {
+ let prop_ty = crate::schema_ref_type_name(spec, additional_schema)?;
fields.push_str("#[serde(flatten)]\n");
fields.push_str("pub additional: BTreeMap<String, ");
fields.push_str(&prop_ty);
diff --git a/src/generated/structs.rs b/src/generated/structs.rs
index 1445c47..e727b5d 100644
--- a/src/generated/structs.rs
+++ b/src/generated/structs.rs
@@ -2278,7 +2278,8 @@ pub struct Repository {
pub release_counter: Option<u64>,
pub repo_transfer: Option<RepoTransfer>,
pub size: Option<u64>,
- pub ssh_url: Option<String>,
+ #[serde(deserialize_with = "crate::deserialize_optional_ssh_url")]
+ pub ssh_url: Option<url::Url>,
pub stars_count: Option<u64>,
pub template: Option<bool>,
#[serde(with = "time::serde::rfc3339::option")]
diff --git a/src/lib.rs b/src/lib.rs
index 90d1137..761c2fa 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
use reqwest::{Client, Request, StatusCode};
+use serde::{Deserialize, Deserializer};
use soft_assert::*;
use url::Url;
use zeroize::Zeroize;
@@ -254,6 +255,41 @@ fn none_if_blank_url<'de, D: serde::Deserializer<'de>>(
deserializer.deserialize_str(EmptyUrlVisitor)
}
+#[allow(dead_code)] // not used yet, but it might appear in the future
+fn deserialize_ssh_url<'de, D, DE>(deserializer: D) -> Result<Url, DE>
+where
+ D: Deserializer<'de>,
+ DE: serde::de::Error,
+{
+ let raw_url: String = String::deserialize(deserializer).map_err(DE::custom)?;
+ parse_ssh_url(&raw_url).map_err(DE::custom)
+}
+
+fn deserialize_optional_ssh_url<'de, D, DE>(deserializer: D) -> Result<Option<Url>, DE>
+where
+ D: Deserializer<'de>,
+ DE: serde::de::Error,
+{
+ let raw_url: Option<String> = Option::deserialize(deserializer).map_err(DE::custom)?;
+ raw_url
+ .as_ref()
+ .map(parse_ssh_url)
+ .map(|res| res.map_err(DE::custom))
+ .transpose()
+ .or(Ok(None))
+}
+
+fn parse_ssh_url(raw_url: &String) -> Result<Url, url::ParseError> {
+ // in case of a non-standard ssh-port (not 22), the ssh url coming from the forgejo API
+ // is actually parseable by the url crate, so try to do that first
+ Url::parse(raw_url).or_else(|_| {
+ // otherwise the ssh url is not parseable by the url crate and we try again after some
+ // pre-processing
+ let url = format!("ssh://{url}", url = raw_url.replace(":", "/"));
+ Url::parse(url.as_str())
+ })
+}
+
impl From<structs::DefaultMergeStyle> for structs::MergePullRequestOptionDo {
fn from(value: structs::DefaultMergeStyle) -> Self {
match value {
diff --git a/tests/ci_test.rs b/tests/ci_test.rs
index 6eca774..d51a274 100644
--- a/tests/ci_test.rs
+++ b/tests/ci_test.rs
@@ -430,3 +430,9 @@ async fn admin() {
.await
.expect("failed to delete hook");
}
+
+#[test]
+fn ssh_url_deserialization() {
+ let data = include_str!("./repo_data.json");
+ assert!(serde_json::from_str::<Repository>(data).is_ok());
+}
diff --git a/tests/repo_data.json b/tests/repo_data.json
new file mode 100644
index 0000000..81e94de
--- /dev/null
+++ b/tests/repo_data.json
@@ -0,0 +1,91 @@
+{
+ "id": 160106,
+ "owner": {
+ "id": 94809,
+ "login": "Cyborus",
+ "login_name": "",
+ "full_name": "",
+ "email": "cyborus@noreply.codeberg.org",
+ "avatar_url": "https://codeberg.org/avatars/53e78f627539c6a0b96854028529779133724a5df2d2c229e5d0eb48aaa3d1fa",
+ "language": "",
+ "is_admin": false,
+ "last_login": "0001-01-01T00:00:00Z",
+ "created": "2023-04-30T00:54:15Z",
+ "restricted": false,
+ "active": false,
+ "prohibit_login": false,
+ "location": "",
+ "pronouns": "",
+ "website": "https://cyborus.xyz",
+ "description": "I host my own Forgejo instance at https://code.cartoon-aa.xyz/",
+ "visibility": "public",
+ "followers_count": 4,
+ "following_count": 4,
+ "starred_repos_count": 8,
+ "username": "Cyborus"
+ },
+ "name": "forgejo-api",
+ "full_name": "Cyborus/forgejo-api",
+ "description": "Rust crate to interact with the Forgejo API",
+ "empty": false,
+ "private": false,
+ "fork": false,
+ "template": false,
+ "parent": null,
+ "mirror": false,
+ "size": 1481,
+ "language": "Rust",
+ "languages_url": "https://codeberg.org/api/v1/repos/Cyborus/forgejo-api/languages",
+ "html_url": "https://codeberg.org/Cyborus/forgejo-api",
+ "url": "https://codeberg.org/api/v1/repos/Cyborus/forgejo-api",
+ "link": "",
+ "ssh_url": "git@codeberg.org:Cyborus/forgejo-api.git",
+ "clone_url": "https://codeberg.org/Cyborus/forgejo-api.git",
+ "original_url": "",
+ "website": "",
+ "stars_count": 4,
+ "forks_count": 1,
+ "watchers_count": 2,
+ "open_issues_count": 2,
+ "open_pr_counter": 0,
+ "release_counter": 2,
+ "default_branch": "main",
+ "archived": false,
+ "created_at": "2023-11-09T17:42:18Z",
+ "updated_at": "2024-04-27T22:42:52Z",
+ "archived_at": "1970-01-01T00:00:00Z",
+ "permissions": {
+ "admin": false,
+ "push": false,
+ "pull": true
+ },
+ "has_issues": true,
+ "internal_tracker": {
+ "enable_time_tracker": true,
+ "allow_only_contributors_to_track_time": true,
+ "enable_issue_dependencies": true
+ },
+ "has_wiki": false,
+ "wiki_branch": "master",
+ "has_pull_requests": true,
+ "has_projects": true,
+ "has_releases": true,
+ "has_packages": false,
+ "has_actions": false,
+ "ignore_whitespace_conflicts": false,
+ "allow_merge_commits": true,
+ "allow_rebase": true,
+ "allow_rebase_explicit": true,
+ "allow_squash_merge": true,
+ "allow_fast_forward_only_merge": false,
+ "allow_rebase_update": true,
+ "default_delete_branch_after_merge": true,
+ "default_merge_style": "merge",
+ "default_allow_maintainer_edit": true,
+ "avatar_url": "",
+ "internal": false,
+ "mirror_interval": "",
+ "object_format_name": "sha1",
+ "mirror_updated": "0001-01-01T00:00:00Z",
+ "repo_transfer": null
+}