From 9b2864bd70e7bff52f4f0bbdfbe0e2164d286063 Mon Sep 17 00:00:00 2001 From: aviac Date: Fri, 10 May 2024 09:00:27 +0200 Subject: [PATCH 1/4] feat: naive implementation of ssh_url deserialization --- generator/src/structs.rs | 17 +++++++++++++---- src/generated/structs.rs | 3 ++- src/lib.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 41 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" { - 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, pub repo_transfer: Option, pub size: Option, - pub ssh_url: Option, + #[serde(deserialize_with = "crate::deserialize_optional_ssh_url")] + pub ssh_url: Option, pub stars_count: Option, pub template: Option, #[serde(with = "time::serde::rfc3339::option")] diff --git a/src/lib.rs b/src/lib.rs index 90d1137..c86ef64 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,31 @@ 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 +where + D: Deserializer<'de>, + DE: serde::de::Error, +{ + let raw_url: String = String::deserialize(deserializer).map_err(DE::custom)?; + let url = format!("ssh://{url}", url = raw_url.replace(":", "/")); + Url::parse(url.as_str()).map_err(DE::custom) +} + +fn deserialize_optional_ssh_url<'de, D, DE>(deserializer: D) -> Result, DE> +where + D: Deserializer<'de>, + DE: serde::de::Error, +{ + let raw_url: Option = Option::deserialize(deserializer).map_err(DE::custom)?; + raw_url + .map(|raw_url| { + let url = format!("ssh://{url}", url = raw_url.replace(":", "/")); + Url::parse(url.as_str()).map_err(DE::custom).map(Some) + }) + .unwrap_or(Ok(None)) +} + impl From for structs::MergePullRequestOptionDo { fn from(value: structs::DefaultMergeStyle) -> Self { match value { From 5fc28346e699b4b65a6596db386416c04bd1ea04 Mon Sep 17 00:00:00 2001 From: aviac Date: Fri, 10 May 2024 09:05:37 +0200 Subject: [PATCH 2/4] =?UTF-8?q?test:=20add=20a=20small=20=F0=9F=A4=8F=20cu?= =?UTF-8?q?te=20=F0=9F=A5=BA=20test=20to=20prove=20it=20works?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/ci_test.rs | 6 +++ tests/repo_data.json | 91 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tests/repo_data.json 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::(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 +} From a786cd85add0acb89c1f4b843f0af6b3f7ebc65a Mon Sep 17 00:00:00 2001 From: aviac Date: Sat, 11 May 2024 13:00:17 +0200 Subject: [PATCH 3/4] fix: implement review comment regarding non standard ssh port scenario --- src/lib.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c86ef64..2463d2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -262,8 +262,7 @@ where DE: serde::de::Error, { let raw_url: String = String::deserialize(deserializer).map_err(DE::custom)?; - let url = format!("ssh://{url}", url = raw_url.replace(":", "/")); - Url::parse(url.as_str()).map_err(DE::custom) + parse_ssh_url(&raw_url).map_err(DE::custom) } fn deserialize_optional_ssh_url<'de, D, DE>(deserializer: D) -> Result, DE> @@ -273,13 +272,23 @@ where { let raw_url: Option = Option::deserialize(deserializer).map_err(DE::custom)?; raw_url - .map(|raw_url| { - let url = format!("ssh://{url}", url = raw_url.replace(":", "/")); - Url::parse(url.as_str()).map_err(DE::custom).map(Some) - }) + .as_ref() + .map(parse_ssh_url) + .map(|res| res.map_err(DE::custom).map(Some)) .unwrap_or(Ok(None)) } +fn parse_ssh_url(raw_url: &String) -> Result { + // 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 for structs::MergePullRequestOptionDo { fn from(value: structs::DefaultMergeStyle) -> Self { match value { From 8188ba5f43e9d0e072d0e97a60f6efb1faae1d49 Mon Sep 17 00:00:00 2001 From: aviac Date: Sat, 11 May 2024 13:02:06 +0200 Subject: [PATCH 4/4] =?UTF-8?q?chore:=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2463d2c..761c2fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -274,8 +274,9 @@ where raw_url .as_ref() .map(parse_ssh_url) - .map(|res| res.map_err(DE::custom).map(Some)) - .unwrap_or(Ok(None)) + .map(|res| res.map_err(DE::custom)) + .transpose() + .or(Ok(None)) } fn parse_ssh_url(raw_url: &String) -> Result {