generate query structs
This commit is contained in:
parent
f30b4eba30
commit
c17c1f8638
|
@ -5,8 +5,8 @@ mod openapi;
|
||||||
use eyre::Context;
|
use eyre::Context;
|
||||||
use heck::{ToPascalCase, ToSnakeCase};
|
use heck::{ToPascalCase, ToSnakeCase};
|
||||||
use openapi::{
|
use openapi::{
|
||||||
MaybeRef, OpenApiV2, Operation, Parameter, ParameterIn, ParameterType, Primitive, Response,
|
CollectionFormat, Items, MaybeRef, OpenApiV2, Operation, Parameter, ParameterIn, ParameterType,
|
||||||
Schema, SchemaType,
|
Primitive, Response, Schema, SchemaType,
|
||||||
};
|
};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
@ -26,6 +26,10 @@ fn main() -> eyre::Result<()> {
|
||||||
s.push_str(&strukt);
|
s.push_str(&strukt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (path, item) in &spec.paths {
|
||||||
|
let strukt = create_query_structs_for_path(&spec, path, item)?;
|
||||||
|
s.push_str(&strukt);
|
||||||
|
}
|
||||||
save_generated(&mut s)?;
|
save_generated(&mut s)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -84,7 +88,9 @@ fn fn_signature_from_op(spec: &OpenApiV2, op: &Operation) -> eyre::Result<String
|
||||||
.replace("o_auth2", "oauth2");
|
.replace("o_auth2", "oauth2");
|
||||||
let args = fn_args_from_op(spec, op)?;
|
let args = fn_args_from_op(spec, op)?;
|
||||||
let ty = fn_return_from_op(spec, op)?;
|
let ty = fn_return_from_op(spec, op)?;
|
||||||
Ok(format!("pub async fn {name}({args}) -> Result<{ty}, ForgejoError>"))
|
Ok(format!(
|
||||||
|
"pub async fn {name}({args}) -> Result<{ty}, ForgejoError>"
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fn_args_from_op(spec: &OpenApiV2, op: &Operation) -> eyre::Result<String> {
|
fn fn_args_from_op(spec: &OpenApiV2, op: &Operation) -> eyre::Result<String> {
|
||||||
|
@ -100,11 +106,11 @@ fn fn_args_from_op(spec: &OpenApiV2, op: &Operation) -> eyre::Result<String> {
|
||||||
};
|
};
|
||||||
match param._in {
|
match param._in {
|
||||||
ParameterIn::Path => {
|
ParameterIn::Path => {
|
||||||
let type_name = path_param_type(¶m)?;
|
let type_name = param_type(¶m, false)?;
|
||||||
args.push_str(", ");
|
args.push_str(", ");
|
||||||
args.push_str(&sanitize_ident(param.name.to_snake_case()));
|
args.push_str(&sanitize_ident(param.name.to_snake_case()));
|
||||||
args.push_str(": ");
|
args.push_str(": ");
|
||||||
args.push_str(type_name);
|
args.push_str(&type_name);
|
||||||
}
|
}
|
||||||
ParameterIn::Query => has_query = true,
|
ParameterIn::Query => has_query = true,
|
||||||
ParameterIn::Header => has_headers = true,
|
ParameterIn::Header => has_headers = true,
|
||||||
|
@ -249,32 +255,71 @@ fn schema_type_name(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_param_type(param: &Parameter) -> eyre::Result<&'static str> {
|
fn param_type(param: &Parameter, owned: bool) -> eyre::Result<String> {
|
||||||
let _type = param
|
let _type = param
|
||||||
._type
|
._type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| eyre::eyre!("no type provided for path param"))?;
|
.ok_or_else(|| eyre::eyre!("no type provided for path param"))?;
|
||||||
let type_name = match _type {
|
param_type_inner(_type, param.format.as_deref(), param.items.as_ref(), owned)
|
||||||
ParameterType::String => match param.format.as_deref() {
|
}
|
||||||
|
|
||||||
|
fn param_type_inner(
|
||||||
|
ty: &ParameterType,
|
||||||
|
format: Option<&str>,
|
||||||
|
items: Option<&Items>,
|
||||||
|
owned: bool,
|
||||||
|
) -> eyre::Result<String> {
|
||||||
|
let ty_name = match ty {
|
||||||
|
ParameterType::String => match format.as_deref() {
|
||||||
Some("date") => "time::Date",
|
Some("date") => "time::Date",
|
||||||
Some("date-time") => "time::OffsetDateTime",
|
Some("date-time") => "time::OffsetDateTime",
|
||||||
_ => "&str",
|
_ => {
|
||||||
},
|
if owned {
|
||||||
ParameterType::Number => match param.format.as_deref() {
|
"String"
|
||||||
|
} else {
|
||||||
|
"&str"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
ParameterType::Number => match format.as_deref() {
|
||||||
Some("float") => "f32",
|
Some("float") => "f32",
|
||||||
Some("double") => "f64",
|
Some("double") => "f64",
|
||||||
_ => "f64",
|
_ => "f64",
|
||||||
},
|
}
|
||||||
ParameterType::Integer => match param.format.as_deref() {
|
.into(),
|
||||||
|
ParameterType::Integer => match format.as_deref() {
|
||||||
Some("int32") => "u32",
|
Some("int32") => "u32",
|
||||||
Some("int64") => "u64",
|
Some("int64") => "u64",
|
||||||
_ => "u32",
|
_ => "u32",
|
||||||
},
|
}
|
||||||
ParameterType::Boolean => "bool",
|
.into(),
|
||||||
ParameterType::Array => eyre::bail!("todo: support returning arrays"),
|
ParameterType::Boolean => "bool".into(),
|
||||||
ParameterType::File => "Vec<u8>",
|
ParameterType::Array => {
|
||||||
|
let item = items
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| eyre::eyre!("array must have item type defined"))?;
|
||||||
|
let item_ty_name = param_type_inner(
|
||||||
|
&item._type,
|
||||||
|
item.format.as_deref(),
|
||||||
|
item.items.as_deref(),
|
||||||
|
owned,
|
||||||
|
)?;
|
||||||
|
if owned {
|
||||||
|
format!("Vec<{item_ty_name}>")
|
||||||
|
} else {
|
||||||
|
format!("&[{item_ty_name}]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParameterType::File => {
|
||||||
|
if owned {
|
||||||
|
format!("Vec<u8>")
|
||||||
|
} else {
|
||||||
|
format!("&[u8]")
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(type_name)
|
Ok(ty_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn method_docs(op: &Operation) -> eyre::Result<String> {
|
fn method_docs(op: &Operation) -> eyre::Result<String> {
|
||||||
|
@ -403,7 +448,7 @@ fn create_method_request(
|
||||||
let mut fmt_args = String::new();
|
let mut fmt_args = String::new();
|
||||||
if has_query {
|
if has_query {
|
||||||
fmt_str.push_str("?{}");
|
fmt_str.push_str("?{}");
|
||||||
fmt_args.push_str(", query");
|
fmt_args.push_str(", query.to_string()");
|
||||||
}
|
}
|
||||||
let path_arg = if fmt_str.contains("{") {
|
let path_arg = if fmt_str.contains("{") {
|
||||||
format!("&format!(\"{fmt_str}\"{fmt_args})")
|
format!("&format!(\"{fmt_str}\"{fmt_args})")
|
||||||
|
@ -579,12 +624,59 @@ fn create_patch_method(spec: &OpenApiV2, path: &str, op: &Operation) -> eyre::Re
|
||||||
|
|
||||||
fn sanitize_ident(mut s: String) -> String {
|
fn sanitize_ident(mut s: String) -> String {
|
||||||
let keywords = [
|
let keywords = [
|
||||||
"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn",
|
"as",
|
||||||
"for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
|
"break",
|
||||||
"return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe",
|
"const",
|
||||||
"use", "where", "while", "abstract", "become", "box", "do", "final", "macro", "override",
|
"continue",
|
||||||
"priv", "typeof", "unsized", "virtual", "yield", "async", "await", "dyn", "try",
|
"crate",
|
||||||
"macro_rules", "union"
|
"else",
|
||||||
|
"enum",
|
||||||
|
"extern",
|
||||||
|
"false",
|
||||||
|
"fn",
|
||||||
|
"for",
|
||||||
|
"if",
|
||||||
|
"impl",
|
||||||
|
"in",
|
||||||
|
"let",
|
||||||
|
"loop",
|
||||||
|
"match",
|
||||||
|
"mod",
|
||||||
|
"move",
|
||||||
|
"mut",
|
||||||
|
"pub",
|
||||||
|
"ref",
|
||||||
|
"return",
|
||||||
|
"self",
|
||||||
|
"Self",
|
||||||
|
"static",
|
||||||
|
"struct",
|
||||||
|
"super",
|
||||||
|
"trait",
|
||||||
|
"true",
|
||||||
|
"type",
|
||||||
|
"unsafe",
|
||||||
|
"use",
|
||||||
|
"where",
|
||||||
|
"while",
|
||||||
|
"abstract",
|
||||||
|
"become",
|
||||||
|
"box",
|
||||||
|
"do",
|
||||||
|
"final",
|
||||||
|
"macro",
|
||||||
|
"override",
|
||||||
|
"priv",
|
||||||
|
"typeof",
|
||||||
|
"unsized",
|
||||||
|
"virtual",
|
||||||
|
"yield",
|
||||||
|
"async",
|
||||||
|
"await",
|
||||||
|
"dyn",
|
||||||
|
"try",
|
||||||
|
"macro_rules",
|
||||||
|
"union",
|
||||||
];
|
];
|
||||||
if s == "self" {
|
if s == "self" {
|
||||||
s = "this".into();
|
s = "this".into();
|
||||||
|
@ -595,7 +687,11 @@ fn sanitize_ident(mut s: String) -> String {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_struct_for_definition(spec: &OpenApiV2, name: &str, schema: &Schema) -> eyre::Result<String> {
|
fn create_struct_for_definition(
|
||||||
|
spec: &OpenApiV2,
|
||||||
|
name: &str,
|
||||||
|
schema: &Schema,
|
||||||
|
) -> eyre::Result<String> {
|
||||||
if matches!(schema._type, Some(SchemaType::One(Primitive::Array))) {
|
if matches!(schema._type, Some(SchemaType::One(Primitive::Array))) {
|
||||||
return Ok(String::new());
|
return Ok(String::new());
|
||||||
}
|
}
|
||||||
|
@ -633,8 +729,197 @@ fn create_struct_docs(schema: &Schema) -> eyre::Result<String> {
|
||||||
out.push_str("\n/// \n");
|
out.push_str("\n/// \n");
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
},
|
}
|
||||||
None => String::new(),
|
None => String::new(),
|
||||||
};
|
};
|
||||||
Ok(doc)
|
Ok(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_query_structs_for_path(
|
||||||
|
spec: &OpenApiV2,
|
||||||
|
path: &str,
|
||||||
|
item: &openapi::PathItem,
|
||||||
|
) -> eyre::Result<String> {
|
||||||
|
let mut s = String::new();
|
||||||
|
if let Some(op) = &item.get {
|
||||||
|
s.push_str(&create_query_struct(spec, path, op).wrap_err("GET")?);
|
||||||
|
}
|
||||||
|
if let Some(op) = &item.put {
|
||||||
|
s.push_str(&create_query_struct(spec, path, op).wrap_err("PUT")?);
|
||||||
|
}
|
||||||
|
if let Some(op) = &item.post {
|
||||||
|
s.push_str(&create_query_struct(spec, path, op).wrap_err("POST")?);
|
||||||
|
}
|
||||||
|
if let Some(op) = &item.delete {
|
||||||
|
s.push_str(&create_query_struct(spec, path, op).wrap_err("DELETE")?);
|
||||||
|
}
|
||||||
|
if let Some(op) = &item.options {
|
||||||
|
s.push_str(&create_query_struct(spec, path, op).wrap_err("OPTIONS")?);
|
||||||
|
}
|
||||||
|
if let Some(op) = &item.head {
|
||||||
|
s.push_str(&create_query_struct(spec, path, op).wrap_err("HEAD")?);
|
||||||
|
}
|
||||||
|
if let Some(op) = &item.patch {
|
||||||
|
s.push_str(&create_query_struct(spec, path, op).wrap_err("PATCH")?);
|
||||||
|
}
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_query_struct(spec: &OpenApiV2, path: &str, op: &Operation) -> eyre::Result<String> {
|
||||||
|
let params = match &op.parameters {
|
||||||
|
Some(params) => params,
|
||||||
|
None => return Ok(String::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut fields = String::new();
|
||||||
|
let mut imp = String::new();
|
||||||
|
imp.push_str("let mut s = String::new();\n");
|
||||||
|
for param in params {
|
||||||
|
let param = match ¶m {
|
||||||
|
MaybeRef::Value { value } => value,
|
||||||
|
MaybeRef::Ref { _ref } => eyre::bail!("todo: add deref parameters"),
|
||||||
|
};
|
||||||
|
if param._in == ParameterIn::Query {
|
||||||
|
let ty = param_type(param, true)?;
|
||||||
|
let field_name = sanitize_ident(param.name.to_snake_case());
|
||||||
|
let required = param.required.unwrap_or_default();
|
||||||
|
fields.push_str(&field_name);
|
||||||
|
fields.push_str(": ");
|
||||||
|
if required {
|
||||||
|
fields.push_str(&ty);
|
||||||
|
} else {
|
||||||
|
fields.push_str("Option<");
|
||||||
|
fields.push_str(&ty);
|
||||||
|
fields.push_str(">");
|
||||||
|
}
|
||||||
|
fields.push_str(",\n");
|
||||||
|
|
||||||
|
let mut handler = String::new();
|
||||||
|
let ty = param
|
||||||
|
._type
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| eyre::eyre!("no type provided for query field"))?;
|
||||||
|
if required {
|
||||||
|
writeln!(&mut handler, "let {field_name} = self.{field_name};")?;
|
||||||
|
} else {
|
||||||
|
writeln!(
|
||||||
|
&mut handler,
|
||||||
|
"if let Some({field_name}) = self.{field_name} {{"
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
match ty {
|
||||||
|
ParameterType::String => match param.format.as_deref() {
|
||||||
|
Some("date-time" | "date") => {
|
||||||
|
writeln!(&mut handler, "s.push_str(\"{}=\");", param.name)?;
|
||||||
|
writeln!(&mut handler, "{field_name}.format_into(&mut s, &time::format_description::well_known::Rfc3339).unwrap();")?;
|
||||||
|
writeln!(&mut handler, "s.push('&');")?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
writeln!(&mut handler, "s.push_str(\"{}=\");", param.name)?;
|
||||||
|
writeln!(&mut handler, "s.push_str(&{field_name});")?;
|
||||||
|
writeln!(&mut handler, "s.push('&');")?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ParameterType::Number | ParameterType::Integer | ParameterType::Boolean => {
|
||||||
|
writeln!(
|
||||||
|
&mut handler,
|
||||||
|
"s.write_fmt(format_args!(\"{}={{}}&\", {field_name})).unwrap();",
|
||||||
|
param.name
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ParameterType::Array => {
|
||||||
|
let format = param.collection_format.unwrap_or(CollectionFormat::Csv);
|
||||||
|
let item = param
|
||||||
|
.items
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| eyre::eyre!("array must have item type defined"))?;
|
||||||
|
let item_pusher = match item._type {
|
||||||
|
ParameterType::String => {
|
||||||
|
match param.format.as_deref() {
|
||||||
|
Some("date-time" | "date") => {
|
||||||
|
"item.format_into(&mut s, &time::format_description::well_known::Rfc3339).unwrap();"
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
"s.push_str(&item);"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ParameterType::Number |
|
||||||
|
ParameterType::Integer |
|
||||||
|
ParameterType::Boolean => {
|
||||||
|
"s.write_fmt(format_args!(\"{{item}}\")).unwrap();"
|
||||||
|
},
|
||||||
|
ParameterType::Array => {
|
||||||
|
eyre::bail!("nested arrays not supported in query");
|
||||||
|
},
|
||||||
|
ParameterType::File => eyre::bail!("cannot send file in query"),
|
||||||
|
};
|
||||||
|
match format {
|
||||||
|
CollectionFormat::Csv => {
|
||||||
|
handler.push_str(&simple_query_array(param, item_pusher, &field_name, ",")?);
|
||||||
|
}
|
||||||
|
CollectionFormat::Ssv => {
|
||||||
|
handler.push_str(&simple_query_array(param, item_pusher, &field_name, " ")?);
|
||||||
|
}
|
||||||
|
CollectionFormat::Tsv => {
|
||||||
|
handler.push_str(&simple_query_array(param, item_pusher, &field_name, "\\t")?);
|
||||||
|
}
|
||||||
|
CollectionFormat::Pipes => {
|
||||||
|
handler.push_str(&simple_query_array(param, item_pusher, &field_name, "|")?);
|
||||||
|
}
|
||||||
|
CollectionFormat::Multi => {
|
||||||
|
writeln!(&mut handler, "")?;
|
||||||
|
writeln!(&mut handler, "if !{field_name}.is_empty() {{")?;
|
||||||
|
writeln!(
|
||||||
|
&mut handler,
|
||||||
|
"for (i, item) in {field_name}.iter().enumerate() {{"
|
||||||
|
)?;
|
||||||
|
writeln!(&mut handler, "s.push_str(\"{}=\");", param.name)?;
|
||||||
|
handler.push_str(item_pusher);
|
||||||
|
handler.push('\n');
|
||||||
|
writeln!(&mut handler, "s.push('&')")?;
|
||||||
|
writeln!(&mut handler, "}}")?;
|
||||||
|
writeln!(&mut handler, "}}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParameterType::File => eyre::bail!("cannot send file in query"),
|
||||||
|
}
|
||||||
|
if !required {
|
||||||
|
writeln!(&mut handler, "}}")?;
|
||||||
|
}
|
||||||
|
imp.push_str(&handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imp.push_str("s\n");
|
||||||
|
if fields.is_empty() {
|
||||||
|
return Ok(String::new());
|
||||||
|
} else {
|
||||||
|
let op_name = op
|
||||||
|
.operation_id
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| eyre::eyre!("no op id found"))?
|
||||||
|
.to_pascal_case();
|
||||||
|
return Ok(format!("pub struct {op_name}Query {{\n{fields}\n}}\n\nimpl {op_name}Query {{\nfn to_string(&self) -> String {{\n{imp}\n}}\n}}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simple_query_array(param: &Parameter, item_pusher: &str, name: &str, sep: &str) -> eyre::Result<String,> {
|
||||||
|
let mut out = String::new();
|
||||||
|
writeln!(&mut out, "s.push_str(\"{}=\");", param.name)?;
|
||||||
|
writeln!(&mut out, "")?;
|
||||||
|
writeln!(&mut out, "if !{name}.is_empty() {{")?;
|
||||||
|
writeln!(
|
||||||
|
&mut out,
|
||||||
|
"for (i, item) in {name}.iter().enumerate() {{"
|
||||||
|
)?;
|
||||||
|
out.push_str(item_pusher);
|
||||||
|
out.push('\n');
|
||||||
|
writeln!(&mut out, "if i < {name}.len() - 1 {{")?;
|
||||||
|
writeln!(&mut out, "s.push('{sep}')")?;
|
||||||
|
writeln!(&mut out, "}}")?;
|
||||||
|
writeln!(&mut out, "}}")?;
|
||||||
|
writeln!(&mut out, "s.push('&')")?;
|
||||||
|
writeln!(&mut out, "}}")?;
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
|
@ -139,7 +139,7 @@ pub enum ParameterType {
|
||||||
File,
|
File,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug, PartialEq)]
|
#[derive(serde::Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||||
#[serde(rename_all(deserialize = "camelCase"))]
|
#[serde(rename_all(deserialize = "camelCase"))]
|
||||||
pub enum CollectionFormat {
|
pub enum CollectionFormat {
|
||||||
Csv,
|
Csv,
|
||||||
|
|
Loading…
Reference in a new issue