1#![allow(dead_code)]
2#![allow(unused_imports)]
3
4use std::{collections::HashSet, ops::Deref, path::PathBuf, sync::Arc};
5
6use anyhow::Context;
7use models::context::MacroCtx;
8use proc_macro::{Span, TokenStream};
9use quote::{format_ident, quote, ToTokens};
10use rodc_util::StructRustType;
11use serde_json::Value;
12use struct_to_struct_mapping::TypeToTypeMapping;
13use syn::{
14 braced, parenthesized, parse::Parse, parse_macro_input, punctuated::Punctuated, token,
15 DeriveInput, Meta, Token,
16};
17use walkdir::WalkDir;
18
19mod models;
20mod rodc_util;
21mod struct_to_struct_mapping;
22
23#[derive(Debug)]
24struct TraitImpl {
25 struct_token: Token![fn],
26 iden: syn::Ident,
27 paren_token: token::Paren,
28 mapping: Request,
29 semi_token: Token![;],
30}
31
32#[derive(Debug, Clone)]
33struct Request {
34 source_type: syn::Path,
35 _coma: syn::Token![->],
36 dest_type: syn::Path,
37}
38
39#[proc_macro]
41pub fn impl_map_fn(input: TokenStream) -> TokenStream {
42 let def = parse_macro_input!(input as TraitImpl);
43 def.into_token_stream().into()
44}
45
46impl Parse for TraitImpl {
47 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
48 let content;
49 let this = Self {
50 struct_token: input.parse()?,
51 iden: input.parse()?,
52 paren_token: parenthesized!(content in input),
53 mapping: content.parse()?,
54 semi_token: input.parse()?,
55 };
56
57 Ok(this)
58 }
59}
60
61impl Parse for Request {
62 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
63 Ok(Self {
64 source_type: input.parse()?,
65 _coma: input.parse()?,
66 dest_type: input.parse()?,
67 })
68 }
69}
70
71impl ToTokens for TraitImpl {
72 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
73 let cargo_toml_path = caller_crate_cargo_toml();
74 let rustdoc_path = cargo_toml_path.parent().unwrap().join("rustdoc.json");
75
76 if !rustdoc_path.exists() {
77 eprintln!(
78 "rustdoc.json does not exist at {:?}, run the cli generate this.",
79 rustdoc_path
80 );
81 tokens.extend(quote! {
82 panic!("rustdoc.json does not exist at {:?}, run the cli generate this.", rustdoc_path);
83 });
84 return;
85 };
86
87 let rdocs = {
88 let content =
89 std::fs::read_to_string(rustdoc_path).expect("read rustdoc.json file from disk");
90 serde_json::from_str::<rustdoc_types::Crate>(&content)
91 .expect("parse rustdoc.json as json")
92 };
93
94 let ctx = MacroCtx::new(rdocs);
95
96 let mapping = TypeToTypeMapping::new(
97 self.mapping.source_type.clone(),
98 vec!["value".to_string()], self.mapping.dest_type.clone(),
100 ctx,
101 )
102 .expect("create struct to struct mapping");
103
104 let fn_name = self.iden.clone();
105 let value_ty = mapping.source.path();
106 let dest_ty = mapping.dest.path();
107
108 let t = quote! {
109 fn #fn_name(value: #value_ty) -> #dest_ty {
110 #mapping
111 }
112 };
113
114 #[cfg(debug_assertions)]
115 tokens.extend(t);
117 }
118}
119
120fn caller_crate_cargo_toml() -> PathBuf {
123 let crate_name =
124 std::env::var("CARGO_PKG_NAME").expect("failed to read ENV var `CARGO_PKG_NAME`!");
125 let current_dir = std::env::current_dir().expect("failed to unwrap env::current_dir()!");
126 let search_entry = format!("name=\"{crate_name}\"");
127 for entry in WalkDir::new(¤t_dir)
128 .into_iter()
129 .filter_entry(|e| !e.file_name().eq_ignore_ascii_case("target"))
130 {
131 let Ok(entry) = entry else { continue };
132 if !entry.file_type().is_file() {
133 continue;
134 }
135 let Some(file_name) = entry.path().file_name() else {
136 continue;
137 };
138 if !file_name.eq_ignore_ascii_case("Cargo.toml") {
139 continue;
140 }
141 let Ok(cargo_toml) = std::fs::read_to_string(entry.path()) else {
142 continue;
143 };
144 if cargo_toml
145 .chars()
146 .filter(|&c| !c.is_whitespace())
147 .collect::<String>()
148 .contains(search_entry.as_str())
149 {
150 return entry.path().to_path_buf();
151 }
152 }
153 current_dir
154}