Skip to content

Commit

Permalink
🐛 Fixed path resolve extractor
Browse files Browse the repository at this point in the history
  • Loading branch information
nwrenger committed Jul 28, 2024
1 parent 7181e52 commit ba8d856
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 96 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gluer"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
authors = ["Nils Wrenger <[email protected]>"]
description = "A wrapper for rust frameworks which addresses the persistent issue of redundant type and function definitions between the frontend and backend"
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Add this to your `Cargo.toml`:

```toml
[dependencies]
light_magic = "0.2.1"
light_magic = "0.2.2"
```

## Disclaimer
Expand All @@ -23,6 +23,7 @@ Please be informed that this crate is in a very early state and is expected to w
- Inferring the input and output types of functions (but only `Json<...>` for inputs)
- Converting them to ts types
- Generating the ts file with the functions and data types
- Using types from other modules works now but only if there are not 2 or more times nested from the router implementation

## How to use

Expand Down
243 changes: 157 additions & 86 deletions src/extractors.rs
Original file line number Diff line number Diff line change
@@ -1,120 +1,191 @@
use std::{collections::HashMap, env::current_dir};
use std::collections::HashMap;

use quote::ToTokens;

use crate::s_err;

pub(crate) fn extract_function(
span: proc_macro2::Span,
fn_name: &str,
file_path: std::path::PathBuf,
file_paths: Vec<std::path::PathBuf>,
) -> syn::Result<(Vec<syn::FnArg>, String)> {
let source = std::fs::read_to_string(file_path)
.map_err(|e| syn::Error::new(proc_macro2::Span::mixed_site(), e.to_string()))?;
let syntax = syn::parse_file(&source)?;

let mut params_map: HashMap<String, Vec<syn::FnArg>> = HashMap::new();
let mut responses_map: HashMap<String, String> = HashMap::new();

for item in syntax.items {
if let syn::Item::Fn(syn::ItemFn { sig, .. }) = item {
let fn_name = sig.ident.to_string();
let params: Vec<syn::FnArg> = sig.inputs.iter().cloned().collect();
params_map.insert(fn_name.clone(), params);

let ty: String = match sig.output {
syn::ReturnType::Default => "()".to_string(),
syn::ReturnType::Type(_, ty) => ty.into_token_stream().to_string(),
};

responses_map.insert(fn_name, ty);
fn extract_from_syntax(
syntax: syn::File,
params_map: &mut HashMap<String, Vec<syn::FnArg>>,
responses_map: &mut HashMap<String, String>,
) -> syn::Result<()> {
for item in syntax.items {
match item {
syn::Item::Fn(syn::ItemFn { sig, .. }) => {
let fn_name = sig.ident.to_string();
let params: Vec<syn::FnArg> = sig.inputs.iter().cloned().collect();
params_map.insert(fn_name.clone(), params);

let ty: String = match sig.output {
syn::ReturnType::Default => "()".to_string(),
syn::ReturnType::Type(_, ty) => ty.into_token_stream().to_string(),
};

responses_map.insert(fn_name, ty);
}
syn::Item::Mod(syn::ItemMod {
content: Some((_, items)),
..
}) => {
extract_from_syntax(
syn::File {
shebang: None,
attrs: vec![],
items,
},
params_map,
responses_map,
)?;
}
_ => {}
}
}
Ok(())
}

let params = params_map.get(fn_name).cloned().ok_or_else(|| {
syn::Error::new(
proc_macro2::Span::call_site(),
"Function parameters not found",
)
})?;
for file_path in file_paths {
let source = std::fs::read_to_string(&file_path)
.map_err(|e| s_err(span, format!("'{}' {e}", file_path.display())))?;
let syntax = syn::parse_file(&source)?;
extract_from_syntax(syntax, &mut params_map, &mut responses_map)?;
}

let params = params_map
.get(fn_name)
.cloned()
.ok_or_else(|| s_err(span, "Function parameters not found"))?;

let responses = responses_map.get(fn_name).cloned().ok_or_else(|| {
syn::Error::new(
proc_macro2::Span::call_site(),
"Function responses not found",
)
})?;
let responses = responses_map
.get(fn_name)
.cloned()
.ok_or_else(|| s_err(span, "Function responses not found"))?;

Ok((params, responses))
}

pub(crate) fn extract_struct(
span: proc_macro2::Span,
struct_name: &str,
file_path: std::path::PathBuf,
file_paths: Vec<std::path::PathBuf>,
) -> syn::Result<Vec<(String, String)>> {
let source = std::fs::read_to_string(&file_path)
.map_err(|e| syn::Error::new(proc_macro2::Span::mixed_site(), e.to_string()))?;
let syntax = syn::parse_file(&source)?;

for item in syntax.items {
if let syn::Item::Struct(syn::ItemStruct { ident, fields, .. }) = item {
let name = ident.to_string().trim().to_string();

if name == struct_name {
let mut field_vec = Vec::new();

if let syn::Fields::Named(fields) = fields {
for field in fields.named {
let field_name = field.ident.unwrap().to_string();
let field_type = field.ty.into_token_stream().to_string();
field_vec.push((field_name, field_type));
fn extract_from_syntax(
syntax: syn::File,
struct_name: &str,
) -> syn::Result<Option<Vec<(String, String)>>> {
for item in syntax.items {
match item {
syn::Item::Struct(syn::ItemStruct { ident, fields, .. }) => {
let name = ident.to_string().trim().to_string();
let name = name.split("::").last().unwrap();

if name == struct_name {
let mut field_vec = Vec::new();

if let syn::Fields::Named(fields) = fields {
for field in fields.named {
let field_name = field.ident.unwrap().to_string();
let field_type = field.ty.into_token_stream().to_string();
field_vec.push((field_name, field_type));
}
}

return Ok(Some(field_vec));
}
}

return Ok(field_vec);
syn::Item::Mod(syn::ItemMod {
content: Some((_, items)),
..
}) => {
if let Some(result) = extract_from_syntax(
syn::File {
shebang: None,
attrs: vec![],
items,
},
struct_name,
)? {
return Ok(Some(result));
}
}
_ => {}
}
}
Ok(None)
}

Err(syn::Error::new(
proc_macro2::Span::call_site(),
"Struct definition not found in ".to_string() + file_path.to_string_lossy().as_ref(),
for file_path in file_paths {
let source = std::fs::read_to_string(&file_path)
.map_err(|e| s_err(span, format!("'{}' {e}", file_path.display())))?;
let syntax = syn::parse_file(&source)?;

if let Some(result) = extract_from_syntax(syntax, struct_name)? {
return Ok(result);
}
}

Err(s_err(
span,
format!("Struct definition not found for {}", struct_name),
))
}

pub(crate) fn resolve_path(segments: Vec<String>) -> syn::Result<std::path::PathBuf> {
let current_dir = current_dir().map_err(|_| {
syn::Error::new(
proc_macro2::Span::call_site(),
"Failed to get current directory",
)
})?;

if segments.len() == 1 {
// Function is in the same file, check if it's in main.rs or lib.rs (for tests)
let main_path = current_dir.join("src/main.rs");
let lib_path = current_dir.join("src/lib.rs");
if main_path.exists() {
Ok(main_path)
} else if lib_path.exists() {
Ok(current_dir.join("tests/main.rs"))
/// Resolves the path to the file containing the module. Note: Two or more nested modules are not supported.
pub(crate) fn resolve_path(
span: proc_macro2::Span,
segments: Vec<String>,
) -> syn::Result<Vec<std::path::PathBuf>> {
let current_dir =
std::env::current_dir().map_err(|_| s_err(span, "Failed to get current directory"))?;
let src_dir = current_dir.join("src");
let test_dir = current_dir.join("tests");
let mut possible_paths = Vec::new();

let working_dir = [src_dir, test_dir];

dbg!(&segments);

for dir in working_dir {
if segments.len() > 2 {
return Err(s_err(
span,
"Twice or more nested modules are currently not supported",
));
} else {
Err(syn::Error::new(
proc_macro2::Span::call_site(),
"Neither main.rs nor lib.rs found",
))?
scan_dir(&dir, &mut possible_paths)?;
}
} else {
// Function is in a different module
let module_path = &segments[0];
let file_path_mod = current_dir.join(format!("src/{}/mod.rs", module_path));
let file_path_alt = current_dir.join(format!("src/{}.rs", module_path));
if file_path_mod.exists() {
Ok(file_path_mod)
} else if file_path_alt.exists() {
Ok(file_path_alt)
} else {
Err(syn::Error::new(
proc_macro2::Span::call_site(),
format!("Module file not found for {}", module_path),
))?
}

if possible_paths.is_empty() {
return Err(s_err(span, "No matching files found"));
}

Ok(possible_paths)
}

fn scan_dir(
dir: &std::path::Path,
possible_paths: &mut Vec<std::path::PathBuf>,
) -> syn::Result<()> {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() && path.extension().map_or(false, |ext| ext == "rs") {
possible_paths.push(path);
} else if path.is_dir() {
let mod_path = path.join("mod.rs");
if mod_path.exists() {
possible_paths.push(mod_path);
}
}
}
}
Ok(())
}
23 changes: 15 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ impl Parse for MethodCall {
let fn_name = r#fn.segments.last().unwrap().ident.to_string();
let segments = r#fn.segments.iter().map(|s| s.ident.to_string()).collect();

let file_path = resolve_path(segments)?;
let file_paths = resolve_path(input.span(), segments)?;

let (params, response) = extract_function(&fn_name, file_path)?;
let (params, response) = extract_function(input.span(), &fn_name, file_paths)?;

Ok(MethodCall {
method,
Expand Down Expand Up @@ -168,22 +168,29 @@ fn gen_spec_inner(input: TokenStream) -> syn::Result<TokenStream> {

for param in &route.params {
if param.contains("Json") {
let struct_name = param
let struct_path = param
.split('<')
.nth(1)
.unwrap()
.split('>')
.next()
.unwrap()
.trim();
.to_string();

let struct_name = struct_path.split("::").last().unwrap().trim().to_string();

param_names.push("data".to_string());
param_types.push(struct_name.to_string());

let file_path =
resolve_path(struct_name.split("::").map(|f| f.to_string()).collect())?;
let file_paths = resolve_path(
span,
struct_path.split("::").map(|f| f.to_string()).collect(),
)?;

let interface =
generate_ts_interface(struct_name, extract_struct(struct_name, file_path)?);
let interface = generate_ts_interface(
&struct_name,
extract_struct(span, &struct_name, file_paths)?,
);
ts_interfaces.push_str(&interface);
} else {
let param_name = param.split(':').next().unwrap().trim().to_string();
Expand Down

0 comments on commit ba8d856

Please sign in to comment.