1use {
2 convert_case::{Case, Casing},
3 std::{fmt::Write, path::Path},
4 tonic_build::Config,
5};
6
7pub fn generate_hooks<P: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path>>(
10 protos: &[P],
11 includes: &[P2],
12 to_path: &Option<P3>,
13 prost_mod: Option<&str>,
14 uri: &str,
15) -> Result<(), std::io::Error> {
16 let mut config = Config::new();
17 let file_descriptor_set = config.load_fds(protos, includes)?;
18
19 for fd in file_descriptor_set.file {
20 let pkg_name = fd
21 .package
22 .as_ref()
23 .map_or_else(|| "_", |string| string.as_str());
24 let filename = format!("{pkg_name}.dx.rs");
25
26 let mut str = format!(
27 "
28 {mod_prost}
29 pub use proto::*;
30 use ::dioxus::prelude::*;
31 ",
32 mod_prost = if let Some(mod_path) = prost_mod {
33 format!(
34 "mod proto {{
35 pub use {mod_path}::{pkg_name}::*;
36 }}"
37 )
38 } else {
39 format!(
40 r#"
41 #[path = "{out_dir}/{pkg_name}.rs"]
42 mod proto;
43 "#,
44 out_dir = std::env::var("OUT_DIR").expect("build.rs"),
45 )
46 },
47 );
48
49
50 for service in &fd.service {
51 let tonic_client = format!(
52 "proto::{}_client::{}Client",
53 service.name().to_case(Case::Snake),
54 service.name().to_case(Case::Pascal)
55 );
56
57 write!(
58 str,
59 "
60 pub struct {service_name}ServiceHook{tonic_client_ty};
61
62 pub fn use_{service_name_lowercase}_service() -> {service_name}ServiceHook {{
63 {service_name}ServiceHook{new_tonic_client}
64 }}
65
66 impl {service_name}ServiceHook {{
67 ",
68 service_name = service.name().to_case(Case::Pascal),
69 service_name_lowercase = service.name().to_case(Case::Snake),
70 tonic_client_ty = {
71 #[cfg(feature = "web")]
72 {
73 format!("({tonic_client}<::tonic_web_wasm_client::Client>)")
74 }
75 #[cfg(not(feature = "web"))]
76 {
77 format!("({tonic_client}<::tonic::transport::Channel>)")
78 }
79 },
80 new_tonic_client = {
81 #[cfg(feature = "web")]
82 {
83 format!(
84 "
85 ({tonic_client}::new(::tonic_web_wasm_client::Client::new(
86 {uri:?}.to_string()
87 )))
88 "
89 )
90 }
91 #[cfg(not(feature = "web"))]
92 {
93 format!(
94 "
95 ({tonic_client}::new(
96 ::tonic::transport::Endpoint::new({uri:?}).unwrap().connect_lazy()
97 ))
98 "
99 )
100 }
101 }
102 )
103 .expect("write error");
104
105 for rpc in &service.method {
106 write!(
107 str,
108 r"
109 pub fn {rpc_name}(&self, req: Signal<{rpc_input}>) -> Resource<Result<{rpc_ouptut}, tonic::Status>> {{
110 let client = self.0.to_owned();
111 use_resource(move || {{
112 let mut client = client.clone();
113 async move {{ client.{rpc_name}(req()).await.map(|resp| resp.into_inner()) }}
114 }})
115 }}
116 ",
117 rpc_name = rpc.name().to_case(Case::Snake),
118 rpc_input = {
119 let mut full_path = rpc.input_type().split('.');
120 let ty = full_path.next_back().expect("build.rs");
121 let path = full_path.filter(|e| !e.is_empty()).collect::<Vec<_>>().join(".");
122
123 if path == pkg_name {
124 format!("proto::{ty}")
125 } else if let Some(mod_path) = prost_mod {
126 format!("{mod_path}::{path}::{ty}")
127 } else {
128 format!("super::{path}::{ty}")
129 }
130 },
131 rpc_ouptut = {
132 let mut full_path = rpc.output_type().split('.');
133 let ty = full_path.next_back().expect("build.rs");
134 let path = full_path.filter(|e| !e.is_empty()).collect::<Vec<_>>().join(".");
135
136 if path == pkg_name {
137 format!("proto::{ty}")
138 } else if let Some(mod_path) = prost_mod {
139 format!("{mod_path}::{path}::{ty}")
140 } else {
141 format!("super::{path}::{ty}")
142 }
143 }
144 ).expect("write error");
145 }
146
147 str.push('}');
148 }
149
150 match to_path {
151 Some(p) => {
152 std::fs::write(
153 {
154 let mut path_to_file = p.as_ref().to_owned();
155 path_to_file.push(filename);
156 path_to_file
157 },
158 str,
159 )
160 },
161 None => {
162 std::fs::write(
163 format!("{}/{filename}", std::env::var("OUT_DIR").expect("build.rs")),
164 str,
165 )
166 },
167 }?;
168 }
169
170
171 Ok(())
172}