From 2a3129688651d815c4870e3e7fdd2853e1f2bfe4 Mon Sep 17 00:00:00 2001 From: nwrenger Date: Sat, 3 Aug 2024 01:27:38 +0200 Subject: [PATCH] :sparkles: Added support for a custom base url --- README.md | 15 ++++++++------- src/lib.rs | 26 +++++++++++++++++++------- tests/api.ts | 8 +++++--- tests/main.rs | 2 +- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 9ebe038..200756f 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,15 @@ Note: This crate is in an early stage and may not work in all cases. Please open - Convert Rust structs to TypeScript interfaces. - Via the `[#metadata]` attribute macro with the `#[meta(...)]` attribute - Generate a TypeScript file with: - - Functions + - Functions to access the api + - Supports a custom base URL - Data types as Interfaces - Generics, even multiple and nested ones, look for that [here](#complete-example) - Using no extra dependencies in the generated TypeScript file. ## How to use -`gluer` generates an api endpoint `.ts` file which expects that you build your frontend statically and host it via `axum`'s static file serving. To use it, follow these steps: +`gluer` generates an api endpoint `.ts` file. To use it, follow these steps: ### Step 1: Define Structs and Functions @@ -111,14 +112,14 @@ let mut app: Api<()> = Api::new() ### Step 3: Generate API -Generate the API file using the `generate_client` function on the `Api` struct. This generates the TypeScript file. +Generate the API file using the `generate_client` function on the `Api` struct. This generates the TypeScript file. You can specify a different `base` and a path, where the file should be generated to and with what name. ```rust,no_run use gluer::Api; let app: Api<()> = Api::new(); -app.generate_client("tests/api.ts"); +app.generate_client("tests/api.ts", ""); ``` ### Step 4: Use the Wrapped Router @@ -193,11 +194,11 @@ async fn add_root( Json(hello.name.to_string()) } -#[tokio::test] -async fn main_test() { +#[tokio::main] +async fn main() { let app: Api<()> = Api::new().route("/:p", extract!(get(fetch_root).post(add_root))); - app.generate_client("tests/api.ts").unwrap(); + app.generate_client("tests/api.ts", "").unwrap(); let _listener = tokio::net::TcpListener::bind("127.0.0.1:8080") .await diff --git a/src/lib.rs b/src/lib.rs index a794e61..6511748 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,9 +66,16 @@ where } } - /// Generate frontend TypeScript API client from the API routes. - pub fn generate_client>(&self, path: P) -> Result<()> { - let fetch_api_function = r#" async function fetch_api(endpoint: string, options: RequestInit): Promise { + /// Generate frontend TypeScript API client from the API routes. Specify the + /// desired `path`, where the file should be generated to and with what name, + /// and the `base` URL. + /// + /// Make sure to never end the `base` on a slash (`/`), resulting in for example + /// a `base` URL like this `""` for using `axum`'s static file hosting or a + /// `base` URL like `"http://localhost:8080"` for a local server. + pub fn generate_client>(&self, path: P, base: &'a str) -> Result<()> { + let base = format!("const BASE = '{}';\n", base); + let basic_functions = r#" async function fetch_api(endpoint: string, options: RequestInit): Promise { const response = await fetch(endpoint, { headers: { "Content-Type": "application/json", @@ -124,7 +131,8 @@ where Self::write_to_file( path, - fetch_api_function, + base.as_str(), + basic_functions, namespace_start, namespace_end, ts_interfaces, @@ -134,7 +142,8 @@ where fn write_to_file>( path: P, - fetch_api_function: &str, + base: &str, + basic_functions: &str, namespace_start: &str, namespace_end: &str, ts_interfaces: BTreeMap, @@ -142,6 +151,9 @@ where ) -> Result<()> { let mut file = File::create(path)?; + file.write_all(base.as_bytes())?; + file.write_all(b"\n").unwrap(); + file.write_all(namespace_start.as_bytes())?; for interface in ts_interfaces.values() { @@ -149,7 +161,7 @@ where file.write_all(b"\n").unwrap(); } - file.write_all(fetch_api_function.as_bytes())?; + file.write_all(basic_functions.as_bytes())?; file.write_all(b"\n").unwrap(); for (i, function) in ts_functions.values().enumerate() { @@ -246,7 +258,7 @@ impl<'a> Route<'a> { format!( r#" export async function {fn_name}({params_str}): Promise<{response_type}> {{ - return fetch_api(`{url}`, {{ + return fetch_api(`${{BASE}}{url}`, {{ method: "{method}", {body_assignment} }}); }} diff --git a/tests/api.ts b/tests/api.ts index b347f1e..53c209b 100644 --- a/tests/api.ts +++ b/tests/api.ts @@ -1,3 +1,5 @@ +const BASE = ''; + namespace api { export interface Age { age: string; @@ -35,15 +37,15 @@ namespace api { return ''; } - export async function add_root(path: number, data: Hello, Huh>, string>): Promise { - return fetch_api(`/${encodeURIComponent(path)}`, { + export async function add_root(path: number, data: Hello, string>, string>): Promise { + return fetch_api(`${BASE}/${encodeURIComponent(path)}`, { method: "POST", body: JSON.stringify(data) }); } export async function fetch_root(queryMap: Record, path: number): Promise { - return fetch_api(`/${encodeURIComponent(path)}?${new URLSearchParams(queryMap).toString()}`, { + return fetch_api(`${BASE}/${encodeURIComponent(path)}?${new URLSearchParams(queryMap).toString()}`, { method: "GET", }); } diff --git a/tests/main.rs b/tests/main.rs index 58a013f..01c7d24 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -51,7 +51,7 @@ async fn add_root( async fn main_test() { let app: Api<()> = Api::new().route("/:p", extract!(get(fetch_root).post(add_root))); - app.generate_client("tests/api.ts").unwrap(); + app.generate_client("tests/api.ts", "").unwrap(); let _listener = tokio::net::TcpListener::bind("127.0.0.1:8080") .await