From 3f1458e1be318cf3389ad8cff604bd3cf690c3fd Mon Sep 17 00:00:00 2001 From: Cyborus Date: Fri, 9 Feb 2024 22:39:32 -0500 Subject: [PATCH] even more strongly typed returns --- generator/src/main.rs | 87 ++++++++++++++++++++++++++++++- generator/src/methods.rs | 15 ++++-- generator/src/structs.rs | 110 +++++++++++++++++++++------------------ src/generated.rs | 26 +++++++-- 4 files changed, 177 insertions(+), 61 deletions(-) diff --git a/generator/src/main.rs b/generator/src/main.rs index 6e43f2c..3aece4f 100644 --- a/generator/src/main.rs +++ b/generator/src/main.rs @@ -4,7 +4,7 @@ mod methods; mod openapi; mod structs; -use heck::ToSnakeCase; +use heck::{ToSnakeCase, ToPascalCase}; use openapi::*; fn main() -> eyre::Result<()> { @@ -51,6 +51,9 @@ fn schema_ref_type_name(spec: &OpenApiV2, schema: &MaybeRef) -> eyre::Re } else { None }; + if name == Some("LanguageStatistics") { + eprintln!("lang stats found"); + } let schema = schema.deref(spec)?; schema_type_name(spec, name, schema) } @@ -184,3 +187,85 @@ fn sanitize_ident(s: &str) -> String { } s } + +fn schema_subtype_name( + spec: &OpenApiV2, + parent_name: &str, + name: &str, + schema: &Schema, + ty: &mut String, +) -> eyre::Result { + let b = match schema { + Schema { + _type: Some(SchemaType::One(Primitive::Object)), + .. + } => { + *ty = format!("{parent_name}{}", name.to_pascal_case()); + true + } + Schema { + _type: Some(SchemaType::One(Primitive::String)), + _enum: Some(_enum), + .. + } => { + *ty = format!("{parent_name}{}", name.to_pascal_case()); + true + } + Schema { + _type: Some(SchemaType::One(Primitive::Array)), + items: Some(items), + .. + } => { + if let MaybeRef::Value { value } = &**items { + if schema_subtype_name(spec, parent_name, name, value, ty)? { + *ty = format!("Vec<{ty}>"); + true + } else { + false + } + } else { + false + } + } + _ => false, + }; + Ok(b) +} +fn schema_subtypes( + spec: &OpenApiV2, + parent_name: &str, + name: &str, + schema: &Schema, + subtypes: &mut Vec, +) -> eyre::Result<()> { + let b = match schema { + Schema { + _type: Some(SchemaType::One(Primitive::Object)), + .. + } => { + let name = format!("{parent_name}{}", name.to_pascal_case()); + let subtype = structs::create_struct_for_definition(spec, &name, schema)?; + subtypes.push(subtype); + } + Schema { + _type: Some(SchemaType::One(Primitive::String)), + _enum: Some(_enum), + .. + } => { + let name = format!("{parent_name}{}", name.to_pascal_case()); + let subtype = structs::create_enum(&name, schema.description.as_deref(), _enum)?; + subtypes.push(subtype); + } + Schema { + _type: Some(SchemaType::One(Primitive::Array)), + items: Some(items), + .. + } => { + if let MaybeRef::Value { value } = &**items { + schema_subtypes(spec, parent_name, name, value, subtypes)?; + } + } + _ => (), + }; + Ok(()) +} diff --git a/generator/src/methods.rs b/generator/src/methods.rs index 28f9c73..23df8c9 100644 --- a/generator/src/methods.rs +++ b/generator/src/methods.rs @@ -1,6 +1,6 @@ use crate::{openapi::*, schema_ref_type_name}; use eyre::{OptionExt, WrapErr}; -use heck::ToSnakeCase; +use heck::{ToPascalCase, ToSnakeCase}; use std::fmt::Write; pub fn create_methods(spec: &OpenApiV2) -> eyre::Result { @@ -270,13 +270,13 @@ fn fn_return_from_op(spec: &OpenApiV2, op: &Operation) -> eyre::Result, + response_ref: &MaybeRef, op: &Operation, ) -> eyre::Result { - let response = schema.deref(spec)?; + let response = response_ref.deref(spec)?; let mut ty = ResponseType::default(); if response.headers.is_some() { - let parent_name = match &schema { + let parent_name = match &response_ref { MaybeRef::Ref { _ref } => _ref .rsplit_once("/") .ok_or_else(|| eyre::eyre!("invalid ref"))? @@ -303,7 +303,12 @@ fn response_ref_type_name( (true, false, false) => { if let Some(schema) = &response.schema { ty.kind = Some(ResponseKind::Json); - ty.body = Some(crate::schema_ref_type_name(spec, schema)?); + let mut body = crate::schema_ref_type_name(spec, schema)?; + if let MaybeRef::Value { value } = schema { + let op_name = op.operation_id.as_deref().ok_or_else(|| eyre::eyre!("no operation id"))?.to_pascal_case(); + crate::schema_subtype_name(spec, &op_name, "Response", value, &mut body)?; + } + ty.body = Some(body); }; } (false, _, true) => { diff --git a/generator/src/structs.rs b/generator/src/structs.rs index ef61a8f..7b7672a 100644 --- a/generator/src/structs.rs +++ b/generator/src/structs.rs @@ -20,11 +20,17 @@ pub fn create_structs(spec: &OpenApiV2) -> eyre::Result { let strukt = create_header_struct(name, headers)?; s.push_str(&strukt); } + + let tys = create_response_struct(spec, name, response)?; + s.push_str(&tys); } } for (_, item) in &spec.paths { let strukt = create_query_structs_for_path(spec, item)?; s.push_str(&strukt); + + let strukt = create_response_structs(spec, item)?; + s.push_str(&strukt); } s.push_str("\n}"); Ok(s) @@ -56,57 +62,8 @@ pub fn create_struct_for_definition( let field_name = crate::sanitize_ident(prop_name); let mut field_ty = prop_ty.clone(); if let MaybeRef::Value { value } = &prop_schema { - fn schema_subtypes( - spec: &OpenApiV2, - ty_name: &str, - name: &str, - schema: &Schema, - subtypes: &mut Vec, - ty: &mut String, - ) -> eyre::Result { - let b = match schema { - Schema { - _type: Some(SchemaType::One(Primitive::Object)), - .. - } => { - let name = format!("{ty_name}{}", name.to_pascal_case()); - let subtype = create_struct_for_definition(spec, &name, schema)?; - subtypes.push(subtype); - *ty = name; - true - } - Schema { - _type: Some(SchemaType::One(Primitive::String)), - _enum: Some(_enum), - .. - } => { - let name = format!("{ty_name}{}", name.to_pascal_case()); - let subtype = create_enum(&name, schema.description.as_deref(), _enum)?; - subtypes.push(subtype); - *ty = name; - true - } - Schema { - _type: Some(SchemaType::One(Primitive::Array)), - items: Some(items), - .. - } => { - if let MaybeRef::Value { value } = &**items { - if schema_subtypes(spec, ty_name, name, value, subtypes, ty)? { - *ty = format!("Vec<{ty}>"); - true - } else { - false - } - } else { - false - } - } - _ => false, - }; - Ok(b) - } - schema_subtypes(spec, name, prop_name, value, &mut subtypes, &mut field_ty)?; + 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" { field_ty = "url::Url".into() @@ -163,7 +120,7 @@ pub fn create_struct_for_definition( Ok(out) } -fn create_enum( +pub fn create_enum( name: &str, desc: Option<&str>, _enum: &[serde_json::Value], @@ -621,3 +578,52 @@ pub fn header_type(header: &Header) -> eyre::Result { true, ) } + +pub fn create_response_structs(spec: &OpenApiV2, item: &PathItem) -> eyre::Result { + let mut s = String::new(); + if let Some(op) = &item.get { + s.push_str(&create_response_structs_for_op(spec, op).wrap_err("GET")?); + } + if let Some(op) = &item.put { + s.push_str(&create_response_structs_for_op(spec, op).wrap_err("PUT")?); + } + if let Some(op) = &item.post { + s.push_str(&create_response_structs_for_op(spec, op).wrap_err("POST")?); + } + if let Some(op) = &item.delete { + s.push_str(&create_response_structs_for_op(spec, op).wrap_err("DELETE")?); + } + if let Some(op) = &item.options { + s.push_str(&create_response_structs_for_op(spec, op).wrap_err("OPTIONS")?); + } + if let Some(op) = &item.head { + s.push_str(&create_response_structs_for_op(spec, op).wrap_err("HEAD")?); + } + if let Some(op) = &item.patch { + s.push_str(&create_response_structs_for_op(spec, op).wrap_err("PATCH")?); + } + Ok(s) +} + +pub fn create_response_structs_for_op(spec: &OpenApiV2, op: &Operation) -> eyre::Result { + let mut out = String::new(); + let op_name = op.operation_id.as_deref().ok_or_else(|| eyre::eyre!("no operation id"))?.to_pascal_case(); + for (_, response) in &op.responses.http_codes { + let response = response.deref(spec)?; + let tys = create_response_struct(spec, &op_name, response)?; + out.push_str(&tys); + } + Ok(out) +} + +pub fn create_response_struct(spec: &OpenApiV2, name: &str, res: &Response) -> eyre::Result { + let mut types = Vec::new(); + if let Some(MaybeRef::Value { value }) = &res.schema { + crate::schema_subtypes(spec, name, "Response", value, &mut types)?; + } + let mut out = String::new(); + for ty in types { + out.push_str(&ty); + } + Ok(out) +} diff --git a/src/generated.rs b/src/generated.rs index 63ee875..05b147c 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -1162,7 +1162,7 @@ impl crate::Forgejo { &self, org: &str, query: TeamSearchQuery, - ) -> Result, ForgejoError> { + ) -> Result { let request = self .get(&format!("orgs/{org}/teams/search?{query}")) .build()?; @@ -4117,7 +4117,7 @@ impl crate::Forgejo { &self, owner: &str, repo: &str, - ) -> Result, ForgejoError> { + ) -> Result { let request = self .get(&format!("repos/{owner}/{repo}/languages")) .build()?; @@ -6918,7 +6918,7 @@ impl crate::Forgejo { pub async fn user_search( &self, query: UserSearchQuery, - ) -> Result, ForgejoError> { + ) -> Result { let request = self.get(&format!("users/search?{query}")).build()?; let response = self.execute(request).await?; match response.status().as_u16() { @@ -10392,6 +10392,11 @@ pub mod structs { }) } } + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + pub struct LanguageStatisticsResponse { + #[serde(flatten)] + pub additional: std::collections::BTreeMap, + } pub struct ErrorHeaders { pub message: Option, @@ -11119,6 +11124,11 @@ pub mod structs { Ok(()) } } + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + pub struct TeamSearchResponse { + pub data: Option>, + pub ok: Option, + } pub struct ListPackagesQuery { /// page number of results to return (1-based) @@ -12363,6 +12373,11 @@ pub mod structs { Ok(()) } } + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + pub struct RepoGetLanguagesResponse { + #[serde(flatten)] + pub additional: std::collections::BTreeMap, + } pub struct RepoGetRawFileOrLfsQuery { /// The name of the commit/branch/tag. Default the repository’s default branch (usually master) @@ -13622,6 +13637,11 @@ pub mod structs { Ok(()) } } + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + pub struct UserSearchResponse { + pub data: Option>, + pub ok: Option, + } pub struct UserListActivityFeedsQuery { /// if true, only show actions performed by the requested user