diff options
-rw-r--r-- | generator/src/structs.rs | 17 | ||||
-rw-r--r-- | src/generated/structs.rs | 3 | ||||
-rw-r--r-- | src/lib.rs | 36 | ||||
-rw-r--r-- | tests/ci_test.rs | 6 | ||||
-rw-r--r-- | tests/repo_data.json | 91 |
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")] @@ -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 +} |