dioxus_grpc/
lib.rs

1use {
2    convert_case::{Case, Casing},
3    std::{fmt::Write, path::Path},
4    tonic_build::Config,
5};
6
7/// - to_path: Is the directory in which files should be written to. When [`None`], defaults to `OUT_DIR`
8/// - prost_mod: If you moved the codegen of proto in a module
9pub 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}