Skip to content

Commit

Permalink
✨ Added support for a custom base url
Browse files Browse the repository at this point in the history
  • Loading branch information
nwrenger committed Aug 2, 2024
1 parent ec0d205 commit 2a31296
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 18 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
26 changes: 19 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,16 @@ where
}
}

/// Generate frontend TypeScript API client from the API routes.
pub fn generate_client<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
let fetch_api_function = r#" async function fetch_api(endpoint: string, options: RequestInit): Promise<any> {
/// 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<P: AsRef<std::path::Path>>(&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<any> {
const response = await fetch(endpoint, {
headers: {
"Content-Type": "application/json",
Expand Down Expand Up @@ -124,7 +131,8 @@ where

Self::write_to_file(
path,
fetch_api_function,
base.as_str(),
basic_functions,
namespace_start,
namespace_end,
ts_interfaces,
Expand All @@ -134,22 +142,26 @@ where

fn write_to_file<P: AsRef<std::path::Path>>(
path: P,
fetch_api_function: &str,
base: &str,
basic_functions: &str,
namespace_start: &str,
namespace_end: &str,
ts_interfaces: BTreeMap<String, String>,
ts_functions: BTreeMap<String, String>,
) -> 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() {
file.write_all(interface.as_bytes())?;
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() {
Expand Down Expand Up @@ -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}
}});
}}
Expand Down
8 changes: 5 additions & 3 deletions tests/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const BASE = '';

namespace api {
export interface Age {
age: string;
Expand Down Expand Up @@ -35,15 +37,15 @@ namespace api {
return '';
}

export async function add_root(path: number, data: Hello<Hello<Huh<Age>, Huh<string>>, string>): Promise<string> {
return fetch_api(`/${encodeURIComponent(path)}`, {
export async function add_root(path: number, data: Hello<Hello<Huh<Age>, string>, string>): Promise<string> {
return fetch_api(`${BASE}/${encodeURIComponent(path)}`, {
method: "POST",
body: JSON.stringify(data)
});
}

export async function fetch_root(queryMap: Record<string, string>, path: number): Promise<string> {
return fetch_api(`/${encodeURIComponent(path)}?${new URLSearchParams(queryMap).toString()}`, {
return fetch_api(`${BASE}/${encodeURIComponent(path)}?${new URLSearchParams(queryMap).toString()}`, {
method: "GET",
});
}
Expand Down
2 changes: 1 addition & 1 deletion tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 2a31296

Please sign in to comment.