use super::generate::GenericErrors;
use codegen::{Function, Scope, Struct};
use convert_case::{Case, Casing};
use lazy_static::lazy_static;
use regex::Regex;
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
lazy_static! {
static ref RE_ARGS: Regex = Regex::new(r#"\{(.*?)\}"#).unwrap();
}
#[derive(Deserialize, Serialize, Debug)]
pub struct TransportHTTP {
endpoint: String,
method: HashMap<String, MethodHTTP>,
}
impl TransportHTTP {
pub fn methods(&self) -> &HashMap<String, MethodHTTP> {
&self.method
}
}
impl MethodHTTP {
pub fn query_method_construct(&self, function_name: &str) -> String {
let mut args = "".to_string();
format!(
"{}MethodQueryArgs {{ {} }}",
function_name.to_case(Case::Pascal),
args
)
}
pub fn body_method_construct(&self, function_name: &str) -> String {
let mut args = "".to_string();
if let Some(body_args) = &self.body_args {
args = body_args
.iter()
.map(|x| format!("{key}: {key}.into()", key = x.to_case(Case::Snake)))
.collect::<Vec<String>>()
.join(", ");
}
format!(
"{}MethodBodyArgs {{ {} }}",
function_name.to_case(Case::Pascal),
args
)
}
pub fn route_method_construct(&self, function_name: &str) -> String {
let args = RE_ARGS
.captures_iter(&self.route)
.map(|x| {
format!(
"{key}: {key}.into()",
key = x[1].to_string().to_case(Case::Snake)
)
})
.collect::<Vec<String>>()
.join(", ");
format!(
"{}MethodRouteArgs {{ {} }}",
function_name.to_case(Case::Pascal),
args
)
}
pub fn generate_method(&self, scope: &mut Scope, endpoint: &str, function_name: &str) -> () {
let mut function = Function::new(&function_name.to_case(Case::Snake));
let body_args_struct_name = format!("{}BodyArgs", function_name.to_case(Case::Pascal));
let query_args_struct_name = format!("{}QueryArgs", function_name.to_case(Case::Pascal));
let route_args_struct_name = format!("{}RouteArgs", function_name.to_case(Case::Pascal));
scope.import("serde", "Serialize");
scope.import("serde", "Deserialize");
let mut body_args_struct = Struct::new(&body_args_struct_name);
body_args_struct
.vis("pub")
.derive("Serialize")
.derive("Deserialize");
let mut query_args_struct = Struct::new(&query_args_struct_name);
query_args_struct
.vis("pub")
.derive("Serialize")
.derive("Deserialize");
let mut route_args_struct = Struct::new(&route_args_struct_name);
route_args_struct
.vis("pub")
.derive("Serialize")
.derive("Deserialize");
if let Some(body_args) = &self.body_args {
let _fields = body_args
.iter()
.map(|x| {
body_args_struct.field(&format!("pub {}", x.to_case(Case::Snake)), "String");
})
.collect::<Vec<()>>();
}
let format_url = RE_ARGS
.captures_iter(&self.route)
.map(|x| {
route_args_struct.field(
&format!("pub {}", x[1].to_string().to_case(Case::Snake)),
"String",
);
format!(
", {key} = route.{key}",
key = x[1].to_string().to_case(Case::Snake)
)
})
.collect::<Vec<String>>()
.join("");
let final_endpoint = format!("{}{}", endpoint, self.route);
let client_method_codegen_line = match self.http_method {
HTTPMethod::GET => format!(
".get(format!(\"{url}\"{format}))",
url = final_endpoint,
format = format_url
),
HTTPMethod::POST => format!(".post(\"{url}\")", url = final_endpoint),
_ => unreachable!(),
};
function
.set_async(true)
.vis("pub")
.generic("T: DeserializeOwned")
.arg("client", "&Client")
.arg("body", &body_args_struct_name)
.arg("query", &query_args_struct_name)
.arg("route", &route_args_struct_name)
.ret("anyhow::Result<T>")
.line(format!(
r#"
let result = client{endpoint}
.body(serde_json::to_string(&body)?)
.query(&query)
.send()
.await
.map_err(|e| anyhow::anyhow!(e))?
.json::<T>()
.await
.map_err(|e| anyhow::anyhow!(e));
result
"#,
endpoint = client_method_codegen_line
));
scope
.push_struct(body_args_struct)
.push_struct(route_args_struct)
.push_struct(query_args_struct)
.push_fn(function);
}
pub fn generate_api_call(&self) -> Result<String, GenericErrors> {
todo!()
}
}
#[derive(Deserialize, Serialize, Debug)]
pub struct TransportGRPC {
endpoint: String,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(tag = "type", content = "info")]
pub enum Transport {
HTTP(TransportHTTP),
GRPC(TransportGRPC),
}
#[derive(Deserialize, Serialize, Debug)]
pub enum HTTPMethod {
GET,
POST,
PUT,
UPDATE,
HEAD,
OPTION,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct MethodHTTP {
route: String,
http_method: HTTPMethod,
body_args: Option<Vec<String>>,
query_args: Option<Vec<String>>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Service {
transport: Transport,
}
impl Service {
pub fn endpoint(&self) -> &str {
match &self.transport {
Transport::HTTP(http) => &http.endpoint,
Transport::GRPC(grpc) => &grpc.endpoint,
}
}
pub fn methods(&self) -> &HashMap<String, MethodHTTP> {
match &self.transport {
Transport::HTTP(http) => http.methods(),
Transport::GRPC(_) => todo!(),
}
}
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Config {
services: HashMap<String, Service>,
}
impl Config {
pub fn get_a_service(&self, name: &str) -> Result<&Service, GenericErrors> {
match self.services.get(name) {
Some(service) => Ok(service),
None => Err(GenericErrors::ServiceNotFoundError(name.to_string())),
}
}
pub fn services(&self) -> &HashMap<String, Service> {
&self.services
}
}
mod test {
use super::*;
#[test]
fn test_config_format() {
let toml_str = r#"
[services]
[services.user.transport]
type = "HTTP"
[services.user.transport.info]
endpoint = "http://truc.io:9009"
[services.user.transport.info.method.test]
route = "api/v3/testMethod"
http_method = "GET"
[services.user.transport.info.method.testPost]
route = "api/v3/testMethod"
http_method = "POST"
body_args = ["status"]
"#;
let config: Result<Config, _> = toml::from_str(toml_str);
dbg!(&config);
assert_eq!(config.is_ok(), true);
}
}