From 8035e7b6a0c925b853fa8b7e601c96b40a929c1a Mon Sep 17 00:00:00 2001 From: myyrakle Date: Fri, 23 Aug 2024 01:09:27 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[#100]=20Swagger=20Json=20=EC=A0=95?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20ToSwaggerDefinitionNode=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + rupring/src/example/domains/users/dto.rs | 11 +- rupring/src/swagger/json.rs | 5 +- rupring/src/swagger/macros.rs | 126 +++++++++++++++++++++++ rupring/src/swagger/mod.rs | 2 + rupring_macro/src/lib.rs | 16 +++ 6 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 rupring/src/swagger/macros.rs diff --git a/.gitignore b/.gitignore index 196e176..5480030 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ Cargo.lock # Added by cargo /target + +test.json \ No newline at end of file diff --git a/rupring/src/example/domains/users/dto.rs b/rupring/src/example/domains/users/dto.rs index 43a752d..99bfd7f 100644 --- a/rupring/src/example/domains/users/dto.rs +++ b/rupring/src/example/domains/users/dto.rs @@ -1,10 +1,19 @@ +use rupring_macro::RequestBody; use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +pub mod foo { + + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize, Deserialize)] + pub struct Bar {} +} + +#[derive(Debug, Serialize, Deserialize, RequestBody)] pub struct CreateUserRequest { pub username: String, pub email: String, pub password: String, + pub bar: foo::Bar, } #[derive(Debug, Serialize, Deserialize)] diff --git a/rupring/src/swagger/json.rs b/rupring/src/swagger/json.rs index d9f8dc0..8b4fda1 100644 --- a/rupring/src/swagger/json.rs +++ b/rupring/src/swagger/json.rs @@ -324,8 +324,10 @@ pub type SwaggerOauth2Scopes = HashMap; pub type SwaggerDefinitions = HashMap; +pub type SwaggerDefinition = SwaggerObjectProperty; + #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct SwaggerDefinition { +pub struct SwaggerObjectProperty { #[serde(rename = "type")] pub type_: String, @@ -337,6 +339,7 @@ pub type SwaggerProperties = HashMap; #[derive(Serialize, Deserialize, Debug, Clone)] pub enum SwaggerProperty { + Object(SwaggerObjectProperty), Array(SwaggerArrayProperty), Single(SwaggerSingleProperty), } diff --git a/rupring/src/swagger/macros.rs b/rupring/src/swagger/macros.rs new file mode 100644 index 0000000..439cabb --- /dev/null +++ b/rupring/src/swagger/macros.rs @@ -0,0 +1,126 @@ +use super::SwaggerDefinition; + +pub trait ToSwaggerDefinitionNode { + fn to_swagger_definition() -> SwaggerDefinitionNode; +} + +pub struct SwaggerDefinitionLeaf { + pub type_: String, +} + +pub enum SwaggerDefinitionNode { + Root(SwaggerDefinition), + Leaf(SwaggerDefinitionLeaf), +} + +impl ToSwaggerDefinitionNode for i8 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for i16 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for i32 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for i64 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for i128 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for u8 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for u16 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for u32 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for u64 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for u128 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for bool { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "boolean".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for f32 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for f64 { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "number".to_string(), + }) + } +} + +impl ToSwaggerDefinitionNode for String { + fn to_swagger_definition() -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + type_: "string".to_string(), + }) + } +} diff --git a/rupring/src/swagger/mod.rs b/rupring/src/swagger/mod.rs index da3d159..71685ec 100644 --- a/rupring/src/swagger/mod.rs +++ b/rupring/src/swagger/mod.rs @@ -7,4 +7,6 @@ pub mod json; pub mod module; pub mod routes; +pub mod macros; + pub use json::*; diff --git a/rupring_macro/src/lib.rs b/rupring_macro/src/lib.rs index ae05457..72afbb6 100644 --- a/rupring_macro/src/lib.rs +++ b/rupring_macro/src/lib.rs @@ -5,6 +5,7 @@ use std::str::FromStr; use attribute::AttributeValue; use proc_macro::TokenStream; +use quote::ToTokens; #[proc_macro_attribute] #[allow(non_snake_case)] @@ -514,3 +515,18 @@ pub fn Patch(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn PatchMapping(attr: TokenStream, item: TokenStream) -> TokenStream { return Patch(attr, item); } + +#[proc_macro_derive(RequestBody, attributes(example, description, desc))] +pub fn derive_request_body(item: TokenStream) -> TokenStream { + let ast = syn::parse_macro_input!(item as syn::ItemStruct); + + for field in ast.fields.iter() { + let field_name = field.ident.as_ref().unwrap().to_string(); + let field_type = field.ty.to_token_stream().to_string(); + + println!("field_name: {}", field_name); + println!("field_type: {}", field_type); + } + + TokenStream::new() +} From 9ad5c1407bd17d5ddbf6e691ba5169c918735330 Mon Sep 17 00:00:00 2001 From: myyrakle Date: Sat, 24 Aug 2024 00:54:18 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[#100]=20Swagger=20Json=20Definition=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rupring/src/example/domains/users/dto.rs | 2 + rupring/src/swagger/json.rs | 8 ++-- rupring_macro/src/lib.rs | 51 +++++++++++++++++++++++- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/rupring/src/example/domains/users/dto.rs b/rupring/src/example/domains/users/dto.rs index 99bfd7f..f01a455 100644 --- a/rupring/src/example/domains/users/dto.rs +++ b/rupring/src/example/domains/users/dto.rs @@ -10,6 +10,8 @@ pub mod foo { #[derive(Debug, Serialize, Deserialize, RequestBody)] pub struct CreateUserRequest { + #[desc = "유저명"] + #[example = "foobar"] pub username: String, pub email: String, pub password: String, diff --git a/rupring/src/swagger/json.rs b/rupring/src/swagger/json.rs index 8b4fda1..8857fb6 100644 --- a/rupring/src/swagger/json.rs +++ b/rupring/src/swagger/json.rs @@ -324,22 +324,24 @@ pub type SwaggerOauth2Scopes = HashMap; pub type SwaggerDefinitions = HashMap; -pub type SwaggerDefinition = SwaggerObjectProperty; +pub type SwaggerDefinition = SwaggerDefinitionObject; #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct SwaggerObjectProperty { +pub struct SwaggerDefinitionObject { #[serde(rename = "type")] pub type_: String, #[serde(rename = "properties")] pub properties: SwaggerProperties, + + #[serde(rename = "required")] + pub required: Vec, } pub type SwaggerProperties = HashMap; #[derive(Serialize, Deserialize, Debug, Clone)] pub enum SwaggerProperty { - Object(SwaggerObjectProperty), Array(SwaggerArrayProperty), Single(SwaggerSingleProperty), } diff --git a/rupring_macro/src/lib.rs b/rupring_macro/src/lib.rs index 72afbb6..36bed0d 100644 --- a/rupring_macro/src/lib.rs +++ b/rupring_macro/src/lib.rs @@ -519,14 +519,63 @@ pub fn PatchMapping(attr: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_derive(RequestBody, attributes(example, description, desc))] pub fn derive_request_body(item: TokenStream) -> TokenStream { let ast = syn::parse_macro_input!(item as syn::ItemStruct); + let struct_name = parse::find_struct_name(&ast); + + let mut code = "".to_string(); + + code += + format!(r#"impl rupring::swagger::macros::ToSwaggerDefinitionNode for {struct_name} {{"#) + .as_str(); + + code += "fn to_swagger_definition() -> rupring::swagger::macros::SwaggerDefinitionNode {"; + + code += format!(r#"let mut swagger_definition = rupring::swagger::json::SwaggerDefinition {{"#) + .as_str(); + code += format!(r#"type_: "object".to_string(),"#).as_str(); + code += format!(r#"properties: std::collections::HashMap::new(),"#).as_str(); + code += "};"; for field in ast.fields.iter() { let field_name = field.ident.as_ref().unwrap().to_string(); let field_type = field.ty.to_token_stream().to_string(); + let attr = field.attrs.clone(); + + // TODO: example 파싱 + // TODO: desc, description 파싱 + // TODO: name 파싱 + + code += + format!(r#"let property_of_type = {field_type}::to_swagger_definition();"#).as_str(); + + code += format!( + r#"let property_value = match property_of_type {{ + rupring::swagger::macros::SwaggerDefinitionNode::Leaf(leaf) => {{ + rupring::swagger::json::SwaggerType {{ + type_: leaf.type_, + }} + }}, + rupring::swagger::macros::SwaggerDefinitionNode::Root(root) => {{ + rupring::swagger::json::SwaggerType {{ + type_: root.type_, + }} + }}, + }};"# + ) + .as_str(); + + code += format!(r#"swagger_definition.properties.insert("{field_name}", property_value)"#) + .as_str(); println!("field_name: {}", field_name); println!("field_type: {}", field_type); + println!("attr: {:?}", attr); } - TokenStream::new() + code += "rupring::swagger::macros::SwaggerDefinitionNode::Root(swagger_definition)"; + + code += "}"; + + code += "}"; + + return TokenStream::from_str(code.as_str()).unwrap(); } From 2918aedc9611696706b7c190c0a87df9ae8c7b3f Mon Sep 17 00:00:00 2001 From: myyrakle Date: Sat, 24 Aug 2024 02:44:22 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[#100]=20RupringDoc=20=EB=A7=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=9C=20=EA=B8=B0=EB=B3=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rupring/src/boot/mod.rs | 2 +- rupring/src/boot/parse.rs | 25 +++++ rupring/src/example/domains/users/dto.rs | 7 +- rupring/src/swagger/context.rs | 2 +- rupring/src/swagger/json.rs | 24 ++++- rupring/src/swagger/macros.rs | 115 ++++++++++++++++------- rupring_macro/src/lib.rs | 63 +++++++++---- 7 files changed, 180 insertions(+), 58 deletions(-) diff --git a/rupring/src/boot/mod.rs b/rupring/src/boot/mod.rs index 7e78cef..4bee167 100644 --- a/rupring/src/boot/mod.rs +++ b/rupring/src/boot/mod.rs @@ -1,6 +1,6 @@ mod banner; pub mod di; -mod parse; +pub mod parse; pub(crate) mod route; use std::collections::HashMap; diff --git a/rupring/src/boot/parse.rs b/rupring/src/boot/parse.rs index b5ee6d6..54a2966 100644 --- a/rupring/src/boot/parse.rs +++ b/rupring/src/boot/parse.rs @@ -1,5 +1,8 @@ use std::collections::HashMap; +use rupring_macro::RupringDoc; +use serde::{Deserialize, Serialize}; + pub(crate) fn parse_query_parameter(raw_querystring: &str) -> HashMap> { let mut query_parameters = HashMap::>::new(); @@ -163,3 +166,25 @@ mod tests { } } } + +pub mod foo { + + use crate as rupring; + + use rupring_macro::RupringDoc; + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize, Deserialize, RupringDoc)] + pub struct Bar {} +} + +use crate as rupring; + +#[derive(Debug, Serialize, Deserialize, RupringDoc)] +pub struct CreateUserRequest { + #[desc = "유저명"] + #[example = "foobar"] + pub username: String, + pub email: String, + pub password: String, + pub bar: foo::Bar, +} diff --git a/rupring/src/example/domains/users/dto.rs b/rupring/src/example/domains/users/dto.rs index f01a455..d59d9eb 100644 --- a/rupring/src/example/domains/users/dto.rs +++ b/rupring/src/example/domains/users/dto.rs @@ -1,14 +1,15 @@ -use rupring_macro::RequestBody; +use rupring_macro::RupringDoc; use serde::{Deserialize, Serialize}; pub mod foo { + use rupring_macro::RupringDoc; use serde::{Deserialize, Serialize}; - #[derive(Debug, Serialize, Deserialize)] + #[derive(Debug, Serialize, Deserialize, RupringDoc)] pub struct Bar {} } -#[derive(Debug, Serialize, Deserialize, RequestBody)] +#[derive(Debug, Serialize, Deserialize, RupringDoc)] pub struct CreateUserRequest { #[desc = "유저명"] #[example = "foobar"] diff --git a/rupring/src/swagger/context.rs b/rupring/src/swagger/context.rs index 36e37de..a877419 100644 --- a/rupring/src/swagger/context.rs +++ b/rupring/src/swagger/context.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, RwLock}; -use crate as rupring; use crate::IModule; +use crate::{self as rupring}; use super::{ json::{SwaggerPath, SwaggerSchema}, diff --git a/rupring/src/swagger/json.rs b/rupring/src/swagger/json.rs index 8857fb6..fcbaf67 100644 --- a/rupring/src/swagger/json.rs +++ b/rupring/src/swagger/json.rs @@ -344,9 +344,10 @@ pub type SwaggerProperties = HashMap; pub enum SwaggerProperty { Array(SwaggerArrayProperty), Single(SwaggerSingleProperty), + Reference(SwaggerReferenceProperty), } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct SwaggerArrayProperty { #[serde(rename = "type")] pub type_: String, @@ -358,7 +359,7 @@ pub struct SwaggerArrayProperty { pub description: String, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct SwaggerSingleProperty { #[serde(rename = "type")] pub type_: String, @@ -367,7 +368,16 @@ pub struct SwaggerSingleProperty { pub description: String, #[serde(rename = "example")] - pub example: Option, + pub example: Option, // TODO: Union으로 치환 +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SwaggerReferenceProperty { + #[serde(rename = "$ref")] + pub reference: String, + + #[serde(rename = "description")] + pub description: String, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -381,3 +391,11 @@ pub enum SwaggerTypeOrReference { Type(SwaggerType), Reference(SwaggerReference), } + +impl Default for SwaggerTypeOrReference { + fn default() -> Self { + SwaggerTypeOrReference::Type(SwaggerType { + type_: "".to_string(), + }) + } +} diff --git a/rupring/src/swagger/macros.rs b/rupring/src/swagger/macros.rs index 439cabb..6fa24e1 100644 --- a/rupring/src/swagger/macros.rs +++ b/rupring/src/swagger/macros.rs @@ -1,7 +1,19 @@ -use super::SwaggerDefinition; +use std::collections::HashSet; + +use super::{ + SwaggerArrayProperty, SwaggerDefinition, SwaggerReference, SwaggerSingleProperty, SwaggerType, + SwaggerTypeOrReference, +}; + +pub struct SwaggerDefinitionContext { + pub property_set: HashSet, +} pub trait ToSwaggerDefinitionNode { - fn to_swagger_definition() -> SwaggerDefinitionNode; + fn to_swagger_definition(context: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode; + fn get_definition_name() -> String { + "".into() + } } pub struct SwaggerDefinitionLeaf { @@ -9,118 +21,157 @@ pub struct SwaggerDefinitionLeaf { } pub enum SwaggerDefinitionNode { - Root(SwaggerDefinition), - Leaf(SwaggerDefinitionLeaf), + Object(SwaggerDefinition), + Array(SwaggerArrayProperty), + Single(SwaggerSingleProperty), } impl ToSwaggerDefinitionNode for i8 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for i16 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for i32 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for i64 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for i128 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for u8 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for u16 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for u32 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for u64 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for u128 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for bool { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "boolean".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for f32 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for f64 { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "number".to_string(), + ..Default::default() }) } } impl ToSwaggerDefinitionNode for String { - fn to_swagger_definition() -> SwaggerDefinitionNode { - SwaggerDefinitionNode::Leaf(SwaggerDefinitionLeaf { + fn to_swagger_definition(_: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + SwaggerDefinitionNode::Single(SwaggerSingleProperty { type_: "string".to_string(), + ..Default::default() + }) + } +} + +impl ToSwaggerDefinitionNode for Vec { + fn to_swagger_definition(context: &mut SwaggerDefinitionContext) -> SwaggerDefinitionNode { + let item = T::to_swagger_definition(context); + + let item = match item { + SwaggerDefinitionNode::Single(item) => { + SwaggerTypeOrReference::Type(SwaggerType { type_: item.type_ }) + } + SwaggerDefinitionNode::Array(item) => item.items, + SwaggerDefinitionNode::Object(_) => { + SwaggerTypeOrReference::Reference(SwaggerReference { + reference: "#/definitions/".to_string() + T::get_definition_name().as_str(), + }) + } + }; + + SwaggerDefinitionNode::Array(SwaggerArrayProperty { + type_: "array".to_string(), + items: item, + ..Default::default() }) } } diff --git a/rupring_macro/src/lib.rs b/rupring_macro/src/lib.rs index 36bed0d..e454559 100644 --- a/rupring_macro/src/lib.rs +++ b/rupring_macro/src/lib.rs @@ -7,6 +7,8 @@ use attribute::AttributeValue; use proc_macro::TokenStream; use quote::ToTokens; +const SHARP: &str = "#"; + #[proc_macro_attribute] #[allow(non_snake_case)] pub fn Module(attr: TokenStream, mut item: TokenStream) -> TokenStream { @@ -516,8 +518,8 @@ pub fn PatchMapping(attr: TokenStream, item: TokenStream) -> TokenStream { return Patch(attr, item); } -#[proc_macro_derive(RequestBody, attributes(example, description, desc))] -pub fn derive_request_body(item: TokenStream) -> TokenStream { +#[proc_macro_derive(RupringDoc, attributes(example, description, desc, required, name))] +pub fn derive_rupring_doc(item: TokenStream) -> TokenStream { let ast = syn::parse_macro_input!(item as syn::ItemStruct); let struct_name = parse::find_struct_name(&ast); @@ -527,12 +529,19 @@ pub fn derive_request_body(item: TokenStream) -> TokenStream { format!(r#"impl rupring::swagger::macros::ToSwaggerDefinitionNode for {struct_name} {{"#) .as_str(); - code += "fn to_swagger_definition() -> rupring::swagger::macros::SwaggerDefinitionNode {"; + code += "fn get_definition_name() -> String {"; + code += r#"let current_module_name = module_path!().to_string();"#; + code += format!(r#"let definition_name = format!("{{current_module_name}}::{struct_name}");"#) + .as_str(); + code += "definition_name"; + code += "}"; + code += "fn to_swagger_definition(context: &mut rupring::swagger::macros::SwaggerDefinitionContext) -> rupring::swagger::macros::SwaggerDefinitionNode {"; code += format!(r#"let mut swagger_definition = rupring::swagger::json::SwaggerDefinition {{"#) .as_str(); code += format!(r#"type_: "object".to_string(),"#).as_str(); code += format!(r#"properties: std::collections::HashMap::new(),"#).as_str(); + code += format!(r#"required: vec![],"#).as_str(); code += "};"; for field in ast.fields.iter() { @@ -542,36 +551,54 @@ pub fn derive_request_body(item: TokenStream) -> TokenStream { // TODO: example 파싱 // TODO: desc, description 파싱 + // TODO: required 파싱 // TODO: name 파싱 - code += - format!(r#"let property_of_type = {field_type}::to_swagger_definition();"#).as_str(); + let description = "".to_string(); + let example = r#""""#.to_string(); + + let name = field_name.clone(); + + code += format!(r#"let property_of_type = {field_type}::to_swagger_definition(context);"#) + .as_str(); code += format!( r#"let property_value = match property_of_type {{ - rupring::swagger::macros::SwaggerDefinitionNode::Leaf(leaf) => {{ - rupring::swagger::json::SwaggerType {{ + rupring::swagger::macros::SwaggerDefinitionNode::Single(leaf) => {{ + rupring::swagger::json::SwaggerProperty::Single(rupring::swagger::json::SwaggerSingleProperty {{ type_: leaf.type_, - }} + description: "{description}".to_string(), + example: Some({example}.to_string()), + }}) }}, - rupring::swagger::macros::SwaggerDefinitionNode::Root(root) => {{ - rupring::swagger::json::SwaggerType {{ - type_: root.type_, - }} + rupring::swagger::macros::SwaggerDefinitionNode::Array(array) => {{ + rupring::swagger::json::SwaggerProperty::Array(rupring::swagger::json::SwaggerArrayProperty {{ + type_: array.type_, + items: array.items, + description: "{description}".to_string(), + }}) + }}, + rupring::swagger::macros::SwaggerDefinitionNode::Object(root) => {{ + let definition_name = {field_type}::get_definition_name(); + + rupring::swagger::json::SwaggerProperty::Reference(rupring::swagger::json::SwaggerReferenceProperty {{ + reference: "{SHARP}/definitions/".to_string() + definition_name.as_str(), + description: "{description}".to_string(), + }}) }}, }};"# ) .as_str(); - code += format!(r#"swagger_definition.properties.insert("{field_name}", property_value)"#) - .as_str(); + code += format!( + r#"swagger_definition.properties.insert("{name}".to_string(), property_value);"# + ) + .as_str(); - println!("field_name: {}", field_name); - println!("field_type: {}", field_type); - println!("attr: {:?}", attr); + //println!("attr: {:?}", attr); } - code += "rupring::swagger::macros::SwaggerDefinitionNode::Root(swagger_definition)"; + code += "rupring::swagger::macros::SwaggerDefinitionNode::Object(swagger_definition)"; code += "}"; From 7e0e46e5f54841e6192c183ab19cd1afcf12cff5 Mon Sep 17 00:00:00 2001 From: myyrakle Date: Sat, 24 Aug 2024 03:05:48 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[#100]=20RupringDoc:=20SwaggerDefinitionObj?= =?UTF-8?q?ect=20=EC=A4=91=EC=B2=A9=20=ED=95=84=EB=93=9C=20generate=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rupring/src/boot/mod.rs | 2 +- rupring/src/boot/parse.rs | 25 --------------------- rupring/src/lib.rs | 12 ++++++++++ rupring/src/swagger/macros.rs | 42 +++++++++++++++++++++++++++++++---- rupring_macro/src/lib.rs | 12 +++++++++- 5 files changed, 62 insertions(+), 31 deletions(-) diff --git a/rupring/src/boot/mod.rs b/rupring/src/boot/mod.rs index 4bee167..7e78cef 100644 --- a/rupring/src/boot/mod.rs +++ b/rupring/src/boot/mod.rs @@ -1,6 +1,6 @@ mod banner; pub mod di; -pub mod parse; +mod parse; pub(crate) mod route; use std::collections::HashMap; diff --git a/rupring/src/boot/parse.rs b/rupring/src/boot/parse.rs index 54a2966..b5ee6d6 100644 --- a/rupring/src/boot/parse.rs +++ b/rupring/src/boot/parse.rs @@ -1,8 +1,5 @@ use std::collections::HashMap; -use rupring_macro::RupringDoc; -use serde::{Deserialize, Serialize}; - pub(crate) fn parse_query_parameter(raw_querystring: &str) -> HashMap> { let mut query_parameters = HashMap::>::new(); @@ -166,25 +163,3 @@ mod tests { } } } - -pub mod foo { - - use crate as rupring; - - use rupring_macro::RupringDoc; - use serde::{Deserialize, Serialize}; - #[derive(Debug, Serialize, Deserialize, RupringDoc)] - pub struct Bar {} -} - -use crate as rupring; - -#[derive(Debug, Serialize, Deserialize, RupringDoc)] -pub struct CreateUserRequest { - #[desc = "유저명"] - #[example = "foobar"] - pub username: String, - pub email: String, - pub password: String, - pub bar: foo::Bar, -} diff --git a/rupring/src/lib.rs b/rupring/src/lib.rs index 813a866..c70abbf 100644 --- a/rupring/src/lib.rs +++ b/rupring/src/lib.rs @@ -537,6 +537,7 @@ pub use request::Request; /// HTTP Response pub use response::Response; use swagger::json::SwaggerOperation; +use swagger::SwaggerDefinitionObject; /// Module interface pub trait IModule { @@ -557,6 +558,13 @@ pub trait IController { fn middlewares(&self) -> Vec; } +pub struct SwaggerRequestBody { + pub definition_name: String, + pub definition_value: SwaggerDefinitionObject, + + pub dependencies: Vec, +} + /// Route interface pub trait IRoute { fn method(&self) -> Method; @@ -566,6 +574,10 @@ pub trait IRoute { fn swagger(&self) -> SwaggerOperation { Default::default() } + + fn swagger_request_body(&self) -> Option { + None + } } /// Handler interface diff --git a/rupring/src/swagger/macros.rs b/rupring/src/swagger/macros.rs index 6fa24e1..6701950 100644 --- a/rupring/src/swagger/macros.rs +++ b/rupring/src/swagger/macros.rs @@ -1,12 +1,14 @@ -use std::collections::HashSet; +use std::collections::HashMap; + +use crate::SwaggerRequestBody; use super::{ - SwaggerArrayProperty, SwaggerDefinition, SwaggerReference, SwaggerSingleProperty, SwaggerType, - SwaggerTypeOrReference, + SwaggerArrayProperty, SwaggerDefinition, SwaggerDefinitionObject, SwaggerReference, + SwaggerSingleProperty, SwaggerType, SwaggerTypeOrReference, }; pub struct SwaggerDefinitionContext { - pub property_set: HashSet, + pub definitions: HashMap, } pub trait ToSwaggerDefinitionNode { @@ -175,3 +177,35 @@ impl ToSwaggerDefinitionNode for Vec { }) } } + +pub fn generate_swagger_request_body() -> Option { + let mut context = SwaggerDefinitionContext { + definitions: Default::default(), + }; + + let root_definition = T::to_swagger_definition(&mut context); + let root_definition_name = T::get_definition_name(); + + let mut swagger_request_body = + if let crate::swagger::macros::SwaggerDefinitionNode::Object(def) = root_definition { + let swagger_request_body = SwaggerRequestBody { + definition_name: root_definition_name, + definition_value: def, + dependencies: vec![], + }; + + swagger_request_body + } else { + return None; + }; + + for (name, definition) in context.definitions { + swagger_request_body.dependencies.push(SwaggerRequestBody { + definition_name: name, + definition_value: definition, + dependencies: vec![], + }); + } + + Some(swagger_request_body) +} diff --git a/rupring_macro/src/lib.rs b/rupring_macro/src/lib.rs index e454559..77c53a2 100644 --- a/rupring_macro/src/lib.rs +++ b/rupring_macro/src/lib.rs @@ -328,6 +328,14 @@ fn MapRoute(method: String, attr: TokenStream, item: TokenStream) -> TokenStream .join(", ") ); + let request_body = additional_attributes + .get("RequestBody") + .map(|e| e.as_string()) + .unwrap_or_default() + .trim_start_matches("\"") + .trim_end_matches("\"") + .to_owned(); + let (item, annotated_parameters) = parse::manipulate_route_function_parameters(item); let mut swagger_code = "".to_string(); @@ -578,9 +586,11 @@ pub fn derive_rupring_doc(item: TokenStream) -> TokenStream { description: "{description}".to_string(), }}) }}, - rupring::swagger::macros::SwaggerDefinitionNode::Object(root) => {{ + rupring::swagger::macros::SwaggerDefinitionNode::Object(object) => {{ let definition_name = {field_type}::get_definition_name(); + context.definitions.insert(definition_name.clone(), object); + rupring::swagger::json::SwaggerProperty::Reference(rupring::swagger::json::SwaggerReferenceProperty {{ reference: "{SHARP}/definitions/".to_string() + definition_name.as_str(), description: "{description}".to_string(), From 8b9ae6fc3290fff558ebff923e2dfd932b79b73b Mon Sep 17 00:00:00 2001 From: myyrakle Date: Sat, 24 Aug 2024 03:15:05 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[#100]=20MapRoute=20=EB=A7=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=9C=EC=97=90=20swagger=5Frequest=5Fbody=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=83=9D=EC=84=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rupring/src/example/domains/users/controller.rs | 1 + rupring/src/lib.rs | 9 +-------- rupring/src/swagger/macros.rs | 9 +++++++-- rupring_macro/src/lib.rs | 13 +++++++++++++ 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/rupring/src/example/domains/users/controller.rs b/rupring/src/example/domains/users/controller.rs index 2f45a9e..74c2897 100644 --- a/rupring/src/example/domains/users/controller.rs +++ b/rupring/src/example/domains/users/controller.rs @@ -39,6 +39,7 @@ pub fn get_user( #[rupring::Post(path = /users)] #[tags = [user]] #[summary = "user 생성"] +#[RequestBody = crate::domains::users::dto::CreateUserRequest] pub fn create_user(request: rupring::Request, _: rupring::Response) -> rupring::Response { let user_service = request.get_provider::>().unwrap(); diff --git a/rupring/src/lib.rs b/rupring/src/lib.rs index c70abbf..385836c 100644 --- a/rupring/src/lib.rs +++ b/rupring/src/lib.rs @@ -537,7 +537,7 @@ pub use request::Request; /// HTTP Response pub use response::Response; use swagger::json::SwaggerOperation; -use swagger::SwaggerDefinitionObject; +use swagger::macros::SwaggerRequestBody; /// Module interface pub trait IModule { @@ -558,13 +558,6 @@ pub trait IController { fn middlewares(&self) -> Vec; } -pub struct SwaggerRequestBody { - pub definition_name: String, - pub definition_value: SwaggerDefinitionObject, - - pub dependencies: Vec, -} - /// Route interface pub trait IRoute { fn method(&self) -> Method; diff --git a/rupring/src/swagger/macros.rs b/rupring/src/swagger/macros.rs index 6701950..7b15088 100644 --- a/rupring/src/swagger/macros.rs +++ b/rupring/src/swagger/macros.rs @@ -1,7 +1,5 @@ use std::collections::HashMap; -use crate::SwaggerRequestBody; - use super::{ SwaggerArrayProperty, SwaggerDefinition, SwaggerDefinitionObject, SwaggerReference, SwaggerSingleProperty, SwaggerType, SwaggerTypeOrReference, @@ -178,6 +176,13 @@ impl ToSwaggerDefinitionNode for Vec { } } +pub struct SwaggerRequestBody { + pub definition_name: String, + pub definition_value: SwaggerDefinitionObject, + + pub dependencies: Vec, +} + pub fn generate_swagger_request_body() -> Option { let mut context = SwaggerDefinitionContext { definitions: Default::default(), diff --git a/rupring_macro/src/lib.rs b/rupring_macro/src/lib.rs index 77c53a2..041a758 100644 --- a/rupring_macro/src/lib.rs +++ b/rupring_macro/src/lib.rs @@ -421,6 +421,17 @@ fn MapRoute(method: String, attr: TokenStream, item: TokenStream) -> TokenStream let route_name = rule::make_route_name(function_name.as_str()); let handler_name = rule::make_handler_name(function_name.as_str()); + let mut swagger_request_body_code = "".to_string(); + if request_body.len() > 0 { + swagger_request_body_code = format!( + r#" + fn swagger_request_body(&self) -> Option {{ + rupring::swagger::macros::generate_swagger_request_body::<{request_body}>() + }} + "# + ); + } + swagger_code.push_str(format!("swagger.summary = \"{summary}\".to_string();").as_str()); swagger_code.push_str(format!("swagger.description = \"{description}\".to_string();").as_str()); swagger_code.push_str(format!("swagger.tags = {tags};", tags = tags).as_str()); @@ -448,6 +459,8 @@ impl rupring::IRoute for {route_name} {{ {swagger_code} swagger }} + + {swagger_request_body_code} }} #[derive(Debug, Clone)] From ca3a3c151d1b59f498bf5259a208f34624195e1c Mon Sep 17 00:00:00 2001 From: myyrakle Date: Sat, 24 Aug 2024 03:27:14 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[#100]=20swagger=5Frequest=5Fbody=EB=A5=BC?= =?UTF-8?q?=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20=EC=83=9D=EC=84=B1=EC=97=90=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rupring/src/swagger/context.rs | 34 +++++++++++++++++++++++++++++++++- rupring/src/swagger/json.rs | 3 +++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/rupring/src/swagger/context.rs b/rupring/src/swagger/context.rs index a877419..ec05e3e 100644 --- a/rupring/src/swagger/context.rs +++ b/rupring/src/swagger/context.rs @@ -7,6 +7,7 @@ use super::{ json::{SwaggerPath, SwaggerSchema}, SwaggerTags, }; +use super::{SwaggerParameter, SwaggerParameterCategory, SwaggerReference, SwaggerTypeOrReference}; #[derive(Debug, Clone, Default)] pub struct SwaggerContext { @@ -54,7 +55,38 @@ fn generate_swagger(swagger: &mut SwaggerSchema, root_module: Box>; pub type SwaggerSecurityDefinitions = HashMap; #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] pub enum SwaggerSecurityDefinition { APIKey(SwaggerAPIKey), Oauth2(SwaggerOauth2), @@ -341,6 +342,7 @@ pub struct SwaggerDefinitionObject { pub type SwaggerProperties = HashMap; #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] pub enum SwaggerProperty { Array(SwaggerArrayProperty), Single(SwaggerSingleProperty), @@ -387,6 +389,7 @@ pub struct SwaggerType { } #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] pub enum SwaggerTypeOrReference { Type(SwaggerType), Reference(SwaggerReference),