strongly typed header returns
This commit is contained in:
parent
311e17e3ba
commit
cb1f2d9ae8
|
@ -1,5 +1,5 @@
|
|||
use crate::openapi::*;
|
||||
use eyre::WrapErr;
|
||||
use eyre::{OptionExt, WrapErr};
|
||||
use heck::ToSnakeCase;
|
||||
use std::fmt::Write;
|
||||
|
||||
|
@ -185,7 +185,7 @@ pub fn param_type(param: &NonBodyParameter, owned: bool) -> eyre::Result<String>
|
|||
)
|
||||
}
|
||||
|
||||
fn param_type_inner(
|
||||
pub fn param_type_inner(
|
||||
ty: &ParameterType,
|
||||
format: Option<&str>,
|
||||
items: Option<&Items>,
|
||||
|
@ -270,7 +270,17 @@ fn response_ref_type_name(
|
|||
let response = schema.deref(spec)?;
|
||||
let mut ty = ResponseType::default();
|
||||
if response.headers.is_some() {
|
||||
ty.headers = Some("reqwest::header::HeaderMap".into());
|
||||
let parent_name = match &schema {
|
||||
MaybeRef::Ref { _ref } => _ref
|
||||
.rsplit_once("/")
|
||||
.ok_or_else(|| eyre::eyre!("invalid ref"))?
|
||||
.1
|
||||
.to_string(),
|
||||
MaybeRef::Value { value: _ } => {
|
||||
eyre::bail!("could not find parent name for header type")
|
||||
}
|
||||
};
|
||||
ty.headers = Some(format!("{}Headers", parent_name));
|
||||
}
|
||||
if let Some(schema) = &response.schema {
|
||||
ty.body = Some(crate::schema_ref_type_name(spec, schema)?);
|
||||
|
@ -422,9 +432,9 @@ fn create_method_response(spec: &OpenApiV2, op: &Operation) -> eyre::Result<Stri
|
|||
.map(|s| s.starts_with("Option<"))
|
||||
.unwrap()
|
||||
{
|
||||
Some("Some(response.headers().clone())")
|
||||
Some("Some(response.headers().try_into()?)")
|
||||
} else {
|
||||
Some("response.headers().clone()")
|
||||
Some("response.headers().try_into()?")
|
||||
}
|
||||
}
|
||||
None => {
|
||||
|
|
|
@ -7,12 +7,21 @@ pub fn create_structs(spec: &OpenApiV2) -> eyre::Result<String> {
|
|||
let mut s = String::new();
|
||||
s.push_str("use structs::*;\n");
|
||||
s.push_str("pub mod structs {\n");
|
||||
s.push_str("use crate::StructureError;");
|
||||
if let Some(definitions) = &spec.definitions {
|
||||
for (name, schema) in definitions {
|
||||
let strukt = create_struct_for_definition(&spec, name, schema)?;
|
||||
s.push_str(&strukt);
|
||||
}
|
||||
}
|
||||
if let Some(responses) = &spec.responses {
|
||||
for (name, response) in responses {
|
||||
if let Some(headers) = &response.headers {
|
||||
let strukt = create_header_struct(name, headers)?;
|
||||
s.push_str(&strukt);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (_, item) in &spec.paths {
|
||||
let strukt = create_query_structs_for_path(item)?;
|
||||
s.push_str(&strukt);
|
||||
|
@ -329,3 +338,119 @@ if !{name}.is_empty() {{
|
|||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn create_header_struct(
|
||||
name: &str,
|
||||
headers: &std::collections::BTreeMap<String, Header>,
|
||||
) -> eyre::Result<String> {
|
||||
let ty_name = format!("{name}Headers").to_pascal_case();
|
||||
let mut fields = String::new();
|
||||
let mut imp = String::new();
|
||||
let mut imp_ret = String::new();
|
||||
for (header_name, header) in headers {
|
||||
let ty = header_type(header)?;
|
||||
let field_name = crate::sanitize_ident(header_name);
|
||||
fields.push_str("pub ");
|
||||
fields.push_str(&field_name);
|
||||
fields.push_str(": Option<");
|
||||
fields.push_str(&ty);
|
||||
fields.push_str(">,\n");
|
||||
|
||||
write!(
|
||||
&mut imp,
|
||||
"
|
||||
let {field_name} = map.get(\"{header_name}\").map(|s| -> Result<_, _> {{
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
"
|
||||
)
|
||||
.unwrap();
|
||||
match &header._type {
|
||||
ParameterType::String => imp.push_str("Ok(s.to_string())"),
|
||||
ParameterType::Number => match header.format.as_deref() {
|
||||
Some("float") => {
|
||||
imp.push_str("s.parse::<f32>().map_err(|_| StructureError::HeaderParseFailed)")
|
||||
}
|
||||
Some("double") | _ => {
|
||||
imp.push_str("s.parse::<f64>()).map_err(|_| StructureError::HeaderParseFailed)")
|
||||
}
|
||||
},
|
||||
ParameterType::Integer => match header.format.as_deref() {
|
||||
Some("int64") => {
|
||||
imp.push_str("s.parse::<u64>().map_err(|_| StructureError::HeaderParseFailed)")
|
||||
}
|
||||
Some("int32") | _ => {
|
||||
imp.push_str("s.parse::<u32>().map_err(|_| StructureError::HeaderParseFailed)")
|
||||
}
|
||||
},
|
||||
ParameterType::Boolean => {
|
||||
imp.push_str("s.parse::<bool>().map_err(|_| StructureError::HeaderParseFailed)")
|
||||
}
|
||||
ParameterType::Array => {
|
||||
let sep = match header.collection_format {
|
||||
Some(CollectionFormat::Csv) | None => ",",
|
||||
Some(CollectionFormat::Ssv) => " ",
|
||||
Some(CollectionFormat::Tsv) => "\\t",
|
||||
Some(CollectionFormat::Pipes) => "|",
|
||||
Some(CollectionFormat::Multi) => {
|
||||
eyre::bail!("multi format not supported in headers")
|
||||
}
|
||||
};
|
||||
let items = header
|
||||
.items
|
||||
.as_ref()
|
||||
.ok_or_else(|| eyre::eyre!("items property must be set for arrays"))?;
|
||||
if items._type == ParameterType::String {
|
||||
imp.push_str("Ok(");
|
||||
}
|
||||
imp.push_str("s.split(\"");
|
||||
imp.push_str(sep);
|
||||
imp.push_str("\").map(|s| ");
|
||||
imp.push_str(match items._type {
|
||||
ParameterType::String => "s.to_string()).collect::<Vec<_>>())",
|
||||
ParameterType::Number => match items.format.as_deref() {
|
||||
Some("float") => "s.parse::<f32>()).collect::<Result<Vec<_>, _>>().map_err(|_| StructureError::HeaderParseFailed)",
|
||||
Some("double") | _ => "s.parse::<f64>()).collect::<Result<Vec<_>, _>>().map_err(|_| StructureError::HeaderParseFailed)",
|
||||
},
|
||||
ParameterType::Integer => match items.format.as_deref() {
|
||||
Some("int64") => "s.parse::<u64>()).collect::<Result<Vec<_>, _>>().map_err(|_| StructureError::HeaderParseFailed)",
|
||||
Some("int32") | _ => "s.parse::<u32>()).collect::<Result<Vec<_>, _>>().map_err(|_| StructureError::HeaderParseFailed)",
|
||||
},
|
||||
ParameterType::Boolean => "s.parse::<bool>()).collect::<Result<Vec<_>, _>>().map_err(|_| StructureError::HeaderParseFailed)",
|
||||
ParameterType::Array => eyre::bail!("nested arrays not supported in headers"),
|
||||
ParameterType::File => eyre::bail!("files not supported in headers"),
|
||||
});
|
||||
}
|
||||
ParameterType::File => eyre::bail!("files not supported in headers"),
|
||||
}
|
||||
imp.push_str("}).transpose()?;");
|
||||
|
||||
imp_ret.push_str(&field_name);
|
||||
imp_ret.push_str(", ");
|
||||
}
|
||||
|
||||
Ok(format!(
|
||||
"
|
||||
pub struct {ty_name} {{
|
||||
{fields}
|
||||
}}
|
||||
|
||||
impl TryFrom<&reqwest::header::HeaderMap> for {ty_name} {{
|
||||
type Error = StructureError;
|
||||
|
||||
fn try_from(map: &reqwest::header::HeaderMap) -> Result<Self, Self::Error> {{
|
||||
{imp}
|
||||
Ok(Self {{ {imp_ret} }})
|
||||
}}
|
||||
}}
|
||||
"
|
||||
))
|
||||
}
|
||||
|
||||
pub fn header_type(header: &Header) -> eyre::Result<String> {
|
||||
crate::methods::param_type_inner(
|
||||
&header._type,
|
||||
header.format.as_deref(),
|
||||
header.items.as_ref(),
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
|
248
src/generated.rs
248
src/generated.rs
|
@ -1818,13 +1818,13 @@ impl crate::Forgejo {
|
|||
owner: &str,
|
||||
repo: &str,
|
||||
query: RepoGetAllCommitsQuery,
|
||||
) -> Result<(reqwest::header::HeaderMap, Vec<Commit>), ForgejoError> {
|
||||
) -> Result<(CommitListHeaders, Vec<Commit>), ForgejoError> {
|
||||
let request = self
|
||||
.get(&format!("repos/{owner}/{repo}/commits?{query}"))
|
||||
.build()?;
|
||||
let response = self.execute(request).await?;
|
||||
match response.status().as_u16() {
|
||||
200 => Ok((response.headers().clone(), response.json().await?)),
|
||||
200 => Ok((response.headers().try_into()?, response.json().await?)),
|
||||
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
|
||||
}
|
||||
}
|
||||
|
@ -4471,7 +4471,7 @@ impl crate::Forgejo {
|
|||
repo: &str,
|
||||
index: u64,
|
||||
query: RepoGetPullRequestCommitsQuery,
|
||||
) -> Result<(reqwest::header::HeaderMap, Vec<Commit>), ForgejoError> {
|
||||
) -> Result<(CommitListHeaders, Vec<Commit>), ForgejoError> {
|
||||
let request = self
|
||||
.get(&format!(
|
||||
"repos/{owner}/{repo}/pulls/{index}/commits?{query}"
|
||||
|
@ -4479,7 +4479,7 @@ impl crate::Forgejo {
|
|||
.build()?;
|
||||
let response = self.execute(request).await?;
|
||||
match response.status().as_u16() {
|
||||
200 => Ok((response.headers().clone(), response.json().await?)),
|
||||
200 => Ok((response.headers().try_into()?, response.json().await?)),
|
||||
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
|
||||
}
|
||||
}
|
||||
|
@ -4495,13 +4495,13 @@ impl crate::Forgejo {
|
|||
repo: &str,
|
||||
index: u64,
|
||||
query: RepoGetPullRequestFilesQuery,
|
||||
) -> Result<(reqwest::header::HeaderMap, Vec<ChangedFile>), ForgejoError> {
|
||||
) -> Result<(ChangedFileListHeaders, Vec<ChangedFile>), ForgejoError> {
|
||||
let request = self
|
||||
.get(&format!("repos/{owner}/{repo}/pulls/{index}/files?{query}"))
|
||||
.build()?;
|
||||
let response = self.execute(request).await?;
|
||||
match response.status().as_u16() {
|
||||
200 => Ok((response.headers().clone(), response.json().await?)),
|
||||
200 => Ok((response.headers().try_into()?, response.json().await?)),
|
||||
_ => Err(ForgejoError::UnexpectedStatusCode(response.status())),
|
||||
}
|
||||
}
|
||||
|
@ -7217,6 +7217,7 @@ impl crate::Forgejo {
|
|||
}
|
||||
use structs::*;
|
||||
pub mod structs {
|
||||
use crate::StructureError;
|
||||
/// APIError is an api error with a message
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -9623,6 +9624,241 @@ pub mod structs {
|
|||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
pub struct ChangedFileListHeaders {
|
||||
pub x_has_more: Option<bool>,
|
||||
pub x_page: Option<u64>,
|
||||
pub x_page_count: Option<u64>,
|
||||
pub x_per_page: Option<u64>,
|
||||
pub x_total: Option<u64>,
|
||||
}
|
||||
|
||||
impl TryFrom<&reqwest::header::HeaderMap> for ChangedFileListHeaders {
|
||||
type Error = StructureError;
|
||||
|
||||
fn try_from(value: &reqwest::header::HeaderMap) -> Result<Self, Self::Error> {
|
||||
let x_has_more = value
|
||||
.get("X-HasMore")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
s.parse::<bool>()
|
||||
.map_err(|_| StructureError::HeaderParseFailed)
|
||||
})
|
||||
.transpose()?;
|
||||
let x_page = value
|
||||
.get("X-Page")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
s.parse::<u64>()
|
||||
.map_err(|_| StructureError::HeaderParseFailed)
|
||||
})
|
||||
.transpose()?;
|
||||
let x_page_count = value
|
||||
.get("X-PageCount")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
s.parse::<u64>()
|
||||
.map_err(|_| StructureError::HeaderParseFailed)
|
||||
})
|
||||
.transpose()?;
|
||||
let x_per_page = value
|
||||
.get("X-PerPage")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
s.parse::<u64>()
|
||||
.map_err(|_| StructureError::HeaderParseFailed)
|
||||
})
|
||||
.transpose()?;
|
||||
let x_total = value
|
||||
.get("X-Total")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
s.parse::<u64>()
|
||||
.map_err(|_| StructureError::HeaderParseFailed)
|
||||
})
|
||||
.transpose()?;
|
||||
Ok(Self {
|
||||
x_has_more,
|
||||
x_page,
|
||||
x_page_count,
|
||||
x_per_page,
|
||||
x_total,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommitListHeaders {
|
||||
pub x_has_more: Option<bool>,
|
||||
pub x_page: Option<u64>,
|
||||
pub x_page_count: Option<u64>,
|
||||
pub x_per_page: Option<u64>,
|
||||
pub x_total: Option<u64>,
|
||||
}
|
||||
|
||||
impl TryFrom<&reqwest::header::HeaderMap> for CommitListHeaders {
|
||||
type Error = StructureError;
|
||||
|
||||
fn try_from(value: &reqwest::header::HeaderMap) -> Result<Self, Self::Error> {
|
||||
let x_has_more = value
|
||||
.get("X-HasMore")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
s.parse::<bool>()
|
||||
.map_err(|_| StructureError::HeaderParseFailed)
|
||||
})
|
||||
.transpose()?;
|
||||
let x_page = value
|
||||
.get("X-Page")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
s.parse::<u64>()
|
||||
.map_err(|_| StructureError::HeaderParseFailed)
|
||||
})
|
||||
.transpose()?;
|
||||
let x_page_count = value
|
||||
.get("X-PageCount")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
s.parse::<u64>()
|
||||
.map_err(|_| StructureError::HeaderParseFailed)
|
||||
})
|
||||
.transpose()?;
|
||||
let x_per_page = value
|
||||
.get("X-PerPage")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
s.parse::<u64>()
|
||||
.map_err(|_| StructureError::HeaderParseFailed)
|
||||
})
|
||||
.transpose()?;
|
||||
let x_total = value
|
||||
.get("X-Total")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
s.parse::<u64>()
|
||||
.map_err(|_| StructureError::HeaderParseFailed)
|
||||
})
|
||||
.transpose()?;
|
||||
Ok(Self {
|
||||
x_has_more,
|
||||
x_page,
|
||||
x_page_count,
|
||||
x_per_page,
|
||||
x_total,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ErrorHeaders {
|
||||
pub message: Option<String>,
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<&reqwest::header::HeaderMap> for ErrorHeaders {
|
||||
type Error = StructureError;
|
||||
|
||||
fn try_from(value: &reqwest::header::HeaderMap) -> Result<Self, Self::Error> {
|
||||
let message = value
|
||||
.get("message")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
Ok(s.to_string())
|
||||
})
|
||||
.transpose()?;
|
||||
let url = value
|
||||
.get("url")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
Ok(s.to_string())
|
||||
})
|
||||
.transpose()?;
|
||||
Ok(Self { message, url })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ForbiddenHeaders {
|
||||
pub message: Option<String>,
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<&reqwest::header::HeaderMap> for ForbiddenHeaders {
|
||||
type Error = StructureError;
|
||||
|
||||
fn try_from(value: &reqwest::header::HeaderMap) -> Result<Self, Self::Error> {
|
||||
let message = value
|
||||
.get("message")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
Ok(s.to_string())
|
||||
})
|
||||
.transpose()?;
|
||||
let url = value
|
||||
.get("url")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
Ok(s.to_string())
|
||||
})
|
||||
.transpose()?;
|
||||
Ok(Self { message, url })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InvalidTopicsErrorHeaders {
|
||||
pub invalid_topics: Option<Vec<String>>,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<&reqwest::header::HeaderMap> for InvalidTopicsErrorHeaders {
|
||||
type Error = StructureError;
|
||||
|
||||
fn try_from(value: &reqwest::header::HeaderMap) -> Result<Self, Self::Error> {
|
||||
let invalid_topics = value
|
||||
.get("invalidTopics")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
Ok(s.split(",").map(|s| s.to_string()).collect::<Vec<_>>())
|
||||
})
|
||||
.transpose()?;
|
||||
let message = value
|
||||
.get("message")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
Ok(s.to_string())
|
||||
})
|
||||
.transpose()?;
|
||||
Ok(Self {
|
||||
invalid_topics,
|
||||
message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ValidationErrorHeaders {
|
||||
pub message: Option<String>,
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<&reqwest::header::HeaderMap> for ValidationErrorHeaders {
|
||||
type Error = StructureError;
|
||||
|
||||
fn try_from(value: &reqwest::header::HeaderMap) -> Result<Self, Self::Error> {
|
||||
let message = value
|
||||
.get("message")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
Ok(s.to_string())
|
||||
})
|
||||
.transpose()?;
|
||||
let url = value
|
||||
.get("url")
|
||||
.map(|s| -> Result<_, _> {
|
||||
let s = s.to_str().map_err(|_| StructureError::HeaderNotAscii)?;
|
||||
Ok(s.to_string())
|
||||
})
|
||||
.transpose()?;
|
||||
Ok(Self { message, url })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AdminCronListQuery {
|
||||
pub page: Option<u32>,
|
||||
pub limit: Option<u32>,
|
||||
|
|
17
src/lib.rs
17
src/lib.rs
|
@ -23,7 +23,7 @@ pub enum ForgejoError {
|
|||
#[error("API key should be ascii")]
|
||||
KeyNotAscii,
|
||||
#[error("the response from forgejo was not properly structured")]
|
||||
BadStructure(#[source] serde_json::Error, String),
|
||||
BadStructure(#[from] StructureError),
|
||||
#[error("unexpected status code {} {}", .0.as_u16(), .0.canonical_reason().unwrap_or(""))]
|
||||
UnexpectedStatusCode(StatusCode),
|
||||
#[error("{} {}{}", .0.as_u16(), .0.canonical_reason().unwrap_or(""), .1.as_ref().map(|s| format!(": {s}")).unwrap_or_default())]
|
||||
|
@ -32,6 +32,21 @@ pub enum ForgejoError {
|
|||
AuthTooLong,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum StructureError {
|
||||
#[error("{contents}")]
|
||||
Serde {
|
||||
e: serde_json::Error,
|
||||
contents: String,
|
||||
},
|
||||
#[error("failed to find header `{0}`")]
|
||||
HeaderMissing(&'static str),
|
||||
#[error("header was not ascii")]
|
||||
HeaderNotAscii,
|
||||
#[error("failed to parse header")]
|
||||
HeaderParseFailed,
|
||||
}
|
||||
|
||||
/// Method of authentication to connect to the Forgejo host with.
|
||||
pub enum Auth<'a> {
|
||||
/// Application Access Token. Grants access to scope enabled for the
|
||||
|
|
|
@ -168,6 +168,16 @@ async fn repo() {
|
|||
.await
|
||||
.is_ok();
|
||||
assert!(!is_merged, "pr should not yet be merged");
|
||||
let pr_files_query = RepoGetPullRequestFilesQuery {
|
||||
skip_to: None,
|
||||
whitespace: None,
|
||||
page: None,
|
||||
limit: None,
|
||||
};
|
||||
let (_, _) = api
|
||||
.repo_get_pull_request_files("TestingAdmin", "test", pr.number.unwrap(), pr_files_query)
|
||||
.await
|
||||
.unwrap();
|
||||
let merge_opt = MergePullRequestOption {
|
||||
r#do: "merge".into(),
|
||||
merge_commit_id: None,
|
||||
|
|
Loading…
Reference in a new issue