tauri_specta/lib.rs
1//! Typesafe Tauri commands
2//!
3//! ## Install
4//!
5//! ```bash
6//! cargo add specta
7//! cargo add tauri-specta --features javascript,typescript
8//! ```
9//!
10//! ## Adding Specta to custom types
11//!
12//! ```rust
13//! use specta::Type;
14//! use serde::{Deserialize, Serialize};
15//!
16//! // The `specta::Type` macro allows us to understand your types
17//! // We implement `specta::Type` on primitive types for you.
18//! // If you want to use a type from an external crate you may need to enable the feature on Specta.
19//! #[derive(Serialize, Type)]
20//! pub struct MyCustomReturnType {
21//! pub some_field: String,
22//! }
23//!
24//! #[derive(Deserialize, Type)]
25//! pub struct MyCustomArgumentType {
26//! pub foo: String,
27//! pub bar: i32,
28//! }
29//! ```
30//!
31//! ## Annotate your Tauri commands with Specta
32//!
33//! ```rust
34//! # //! #[derive(Serialize, Type)]
35//! # pub struct MyCustomReturnType {
36//! # pub some_field: String,
37//! # }
38//! #[tauri::command]
39//! #[specta::specta] // <-- This bit here
40//! fn greet3() -> MyCustomReturnType {
41//! MyCustomReturnType {
42//! some_field: "Hello World".into(),
43//! }
44//! }
45//!
46//! #[tauri::command]
47//! #[specta::specta] // <-- This bit here
48//! fn greet(name: String) -> String {
49//! format!("Hello {name}!")
50//! }
51//! ```
52//!
53//! ## Export your bindings
54//!
55//! ```rust
56//! # #[specta::specta]
57//! # fn greet() {}
58//! # #[specta::specta]
59//! # fn greet2() {}
60//! # #[specta::specta]
61//! # fn greet3() {}
62//! use specta::collect_types;
63//! use tauri_specta::{ts, js};
64//!
65//! // this example exports your types on startup when in debug mode or in a unit test. You can do whatever.
66//! fn main() {
67//! #[cfg(debug_assertions)]
68//! ts::export(collect_types![greet, greet2, greet3], "../src/bindings.ts").unwrap();
69//!
70//! // or export to JS with JSDoc
71//! #[cfg(debug_assertions)]
72//! js::export(collect_types![greet, greet2, greet3], "../src/bindings.js").unwrap();
73//! }
74//!
75//! #[test]
76//! fn export_bindings() {
77//! ts::export(collect_types![greet, greet2, greet3], "../src/bindings.ts").unwrap();
78//! js::export(collect_types![greet, greet2, greet3], "../src/bindings.js").unwrap();
79//! }
80//! ```
81//!
82//! ## Usage on frontend
83//!
84//! ```ts
85//! import * as commands from "./bindings"; // This should point to the file we export from Rust
86//!
87//! await commands.greet("Brendan");
88//! ```
89//!
90#![forbid(unsafe_code)]
91#![warn(clippy::all, clippy::unwrap_used, clippy::panic, missing_docs)]
92#![cfg_attr(docsrs, feature(doc_cfg))]
93
94use std::{
95 borrow::Cow,
96 fs::{self, File},
97 io::Write,
98 marker::PhantomData,
99 path::{Path, PathBuf},
100};
101
102use specta::{
103 functions::FunctionDataType,
104 ts::{ExportConfiguration, TsExportError},
105 ExportError, TypeDefs,
106};
107
108/// The exporter for [Javascript](https://www.javascript.com).
109#[cfg(feature = "javascript")]
110#[cfg_attr(docsrs, doc(cfg(feature = "javascript")))]
111pub mod js;
112
113/// The exporter for [TypeScript](https://www.typescriptlang.org).
114#[cfg(feature = "typescript")]
115#[cfg_attr(docsrs, doc(cfg(feature = "typescript")))]
116pub mod ts;
117
118/// This remains for backwards compatibility. Please use [`specta::collect_types`] instead!
119#[macro_export]
120#[deprecated(
121 note = "Please use `specta::collect_types` instead! This alias will be removed in a future release."
122)]
123macro_rules! collate_types {
124 (type_map: $type_map:ident, $($command:path),*) => {{
125 specta::functions::collect_types!(type_map: $type_map, $($command),*)
126 }};
127 ($($command:path),*) => {{
128 let mut type_map = specta::TypeDefs::default();
129 specta::functions::collect_types!(type_map: type_map, $($command),*)
130 }};
131}
132
133pub(crate) const DO_NOT_EDIT: &str = "// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.";
134
135pub(crate) const CRINGE_ESLINT_DISABLE: &str = "/* eslint-disable */
136";
137
138// TODO
139// #[cfg(doctest)]
140// doc_comment::doctest!("../README.md");
141
142/// A set of functions that produce language-specific code
143pub trait ExportLanguage {
144 /// Type definitions and constants that the generated functions rely on
145 fn globals() -> String;
146
147 /// Renders a collection of [`FunctionDataType`] into a string.
148 fn render_functions(
149 function_types: Vec<FunctionDataType>,
150 cfg: &specta::ts::ExportConfiguration,
151 ) -> Result<String, TsExportError>;
152
153 /// Renders the output of [`globals`], [`render_functions`] and all dependant types into a TypeScript string.
154 fn render(
155 function_types: Vec<FunctionDataType>,
156 type_map: TypeDefs,
157 cfg: &specta::ts::ExportConfiguration,
158 ) -> Result<String, TsExportError>;
159}
160
161/// General exporter, takes a generic for the specific language that is being exported to.
162pub struct Exporter<TLang: ExportLanguage> {
163 macro_data: Result<(Vec<FunctionDataType>, TypeDefs), ExportError>,
164 export_path: PathBuf,
165 cfg: Option<ExportConfiguration>,
166 header: Cow<'static, str>,
167 lang: PhantomData<TLang>,
168}
169
170impl<TLang: ExportLanguage> Exporter<TLang> {
171 /// Creates a new TypeScript exporter
172 pub fn new(
173 macro_data: Result<(Vec<FunctionDataType>, TypeDefs), ExportError>,
174 export_path: impl AsRef<Path>,
175 ) -> Self {
176 Self {
177 macro_data,
178 export_path: export_path.as_ref().into(),
179 cfg: None,
180 header: CRINGE_ESLINT_DISABLE.into(), // TODO: Remove this as a default. SemVer moment.
181 lang: Default::default(),
182 }
183 }
184
185 /// Allows for specifying a custom [`ExportConfiguration`](specta::ts::ExportConfiguration).
186 pub fn with_cfg(mut self, cfg: ExportConfiguration) -> Self {
187 self.cfg = Some(cfg);
188 self
189 }
190
191 /// Allows for specifying a custom header to
192 pub fn with_header(mut self, header: &'static str) -> Self {
193 self.header = header.into();
194 self
195 }
196
197 /// Exports the output of [`internal::render`] for a collection of [`FunctionDataType`] into a TypeScript file.
198 pub fn export(self) -> Result<(), TsExportError> {
199 let Self {
200 macro_data,
201 export_path,
202 cfg,
203 header,
204 ..
205 } = self;
206
207 let (function_types, type_map) = macro_data?;
208
209 if let Some(export_dir) = export_path.parent() {
210 fs::create_dir_all(export_dir)?;
211 }
212
213 let mut file = File::create(export_path)?;
214
215 write!(
216 file,
217 "{}{}",
218 header,
219 TLang::render(function_types, type_map, &cfg.unwrap_or_default())?
220 )?;
221
222 Ok(())
223 }
224}