TS_RS Rust and TypeScript types should now be in parity

This commit is contained in:
Natty 2023-11-03 21:14:17 +01:00
parent c8627a996c
commit a5ab2acca0
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
16 changed files with 116 additions and 92 deletions

View File

@ -1,12 +1,16 @@
import {FrontendApiEndpoint, FrontendApiEndpoints} from "./fe-api";
import { GetNoteById } from "./types/endpoints/GetNoteById";
type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
export interface BackendApiEndpoint<M extends Method, T, R> {
export interface BackendApiEndpoint<
M extends Method,
PP extends string[],
T,
R
> {
method: M;
endpoint: string;
pathParams: [string],
pathParams: PP;
request?: T;
response?: R;
}
@ -18,21 +22,22 @@ function nestedUrlSearchParams(data: any, topLevel: boolean = true): string {
case "boolean":
case "number":
case "symbol":
if (topLevel)
return encodeURIComponent(data.toString()) + "=";
if (topLevel) return encodeURIComponent(data.toString()) + "=";
return data.toString();
case "object":
if (data === null)
return "null";
if (data === null) return "null";
if (Array.isArray(data))
return data.map(d => nestedUrlSearchParams(d, true))
return data
.map((d) => nestedUrlSearchParams(d, true))
.map(encodeURIComponent)
.join("&");
const inner = Object.entries(data)
.map(([k, v]) => [k, nestedUrlSearchParams(v, false)]);
const inner = Object.entries(data).map(([k, v]) => [
k,
nestedUrlSearchParams(v, false),
]);
return new URLSearchParams(inner).toString();
@ -45,8 +50,8 @@ type MagApiErrorCode = "Client:GenericApiError" | string;
export interface MagApiError {
status: number;
code: MagApiErrorCode,
message: string,
code: MagApiErrorCode;
message: string;
}
export class MagApiClient {
@ -56,11 +61,21 @@ export class MagApiClient {
this.baseUrl = baseUrl;
}
async call<M extends Method, T extends BackendApiEndpoint<M, T["request"], T["response"]>>(
endpoint: T["endpoint"],
method: M,
async call<
T extends BackendApiEndpoint<
T["method"] & Method,
T["pathParams"] & string[],
T["request"],
T["response"]
>
>(
{ endpoint, method }: T,
data: T["request"],
pathParams: Record<string, string>,
pathParams: {
[K in keyof T["pathParams"] as T["pathParams"][K] & string]:
| string
| number;
},
token?: string | null | undefined
): Promise<T["response"]> {
type Response = T["response"];
@ -70,6 +85,10 @@ export class MagApiClient {
? `Bearer ${authorizationToken}`
: undefined;
for (const name in pathParams) {
endpoint = endpoint.replace(`:${name}`, `${pathParams[name]}`);
}
let url = `${this.baseUrl}/${endpoint}`;
if (method === "GET") {
@ -98,15 +117,14 @@ export class MagApiClient {
}
})
.catch((e) => {
throw ({
throw {
status: -1,
code: "Client:GenericApiError",
message: e
}) as MagApiError;
message: e,
} as MagApiError;
});
}
}
const a = new MagApiClient("https://aaa");
a.call<"GET", GetNoteById>("", "",{}, {})
const result = await a.call(GetNoteById, { attachments: true }, { id: "aaaa" });

View File

@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { NoteListFilter } from "./NoteListFilter";
export interface GetTimelineReq { limit: bigint, filter: NoteListFilter | null, }
export interface GetTimelineReq { limit?: bigint, filter?: NoteListFilter, }

View File

@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface NoteByIdReq { context: boolean, attachments: boolean, }
export interface NoteByIdReq { context?: boolean, attachments?: boolean, }

View File

@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackNoteMaybeAttachments } from "./packed/PackNoteMaybeAttachments";
export interface NoteDetailExt { parent_note: PackNoteMaybeAttachments | null, renoted_note: PackNoteMaybeAttachments | null, }

View File

@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface UserByIdReq { profile: boolean, pins: boolean, detail: boolean, relation: boolean, auth: boolean, }
export interface UserByIdReq { profile?: boolean, pins?: boolean, detail?: boolean, relation?: boolean, auth?: boolean, }

View File

@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface UserSelfReq { profile: boolean, pins: boolean, detail: boolean, secrets: boolean, }
export interface UserSelfReq { profile?: boolean, pins?: boolean, detail?: boolean, secrets?: boolean, }

View File

@ -2,10 +2,11 @@
import type { NoteByIdReq } from "../NoteByIdReq";
import type { PackNoteMaybeFull } from "../packed/PackNoteMaybeFull";
interface GetNoteById {
export const GetNoteById = {
endpoint: "/notes/:id",
pathParams: ["id"],
method: "GET",
request: NoteByIdReq,
response: PackNoteMaybeFull
pathParams: ["id"] as ["id"],
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
request: undefined as unknown as NoteByIdReq,
response: undefined as unknown as PackNoteMaybeFull
}

View File

@ -2,11 +2,10 @@
import type { GetTimelineReq } from "../GetTimelineReq";
import type { PackNoteMaybeFull } from "../packed/PackNoteMaybeFull";
export interface GetTimeline {
endpoint: "/timeline";
pathParams: [];
method: "GET";
request: GetTimelineReq;
response: Array<PackNoteMaybeFull>;
}
export const GetTimeline = {
endpoint: "/timeline",
pathParams: [] as [],
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
request: undefined as unknown as GetTimelineReq,
response: undefined as unknown as Array<PackNoteMaybeFull>,
};

View File

@ -2,11 +2,11 @@
import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
import type { UserByIdReq } from "../UserByIdReq";
export interface GetUserByAcct {
endpoint: "/users/by-acct/:user_id";
pathParams: ["user_id"];
method: "GET";
request: UserByIdReq;
response: PackUserMaybeAll;
export const GetUserByAcct = {
endpoint: "/users/by-acct/:user_id",
pathParams: ["user_id"] as ["user_id"],
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
request: undefined as unknown as UserByIdReq,
response: undefined as unknown as PackUserMaybeAll
}

View File

@ -2,11 +2,11 @@
import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
import type { UserByIdReq } from "../UserByIdReq";
export interface GetUserById {
endpoint: "/users/:user_id";
pathParams: ["user_id"];
method: "GET";
request: UserByIdReq;
response: PackUserMaybeAll;
export const GetUserById = {
endpoint: "/users/:user_id",
pathParams: ["user_id"] as ["user_id"],
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
request: undefined as unknown as UserByIdReq,
response: undefined as unknown as PackUserMaybeAll
}

View File

@ -2,11 +2,11 @@
import type { PackUserSelfMaybeAll } from "../packed/PackUserSelfMaybeAll";
import type { UserSelfReq } from "../UserSelfReq";
export interface GetUserSelf {
endpoint: "/users/@self";
pathParams: [];
method: "GET";
request: UserSelfReq;
response: PackUserSelfMaybeAll;
export const GetUserSelf = {
endpoint: "/users/@self",
pathParams: [] as [],
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
request: undefined as unknown as UserSelfReq,
response: undefined as unknown as PackUserSelfMaybeAll
}

View File

@ -356,17 +356,18 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
fn decl() -> String {
format!(
"interface {} {{\n \
endpoint: \"{}\";\n \
pathParams: {};\n \
method: \"{}\";\n \
request: {};\n \
response: {};\n\
"const {} = {{\n \
endpoint: \"{}\",\n \
pathParams: {} as {},\n \
method: \"{}\" as \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\",\n \
request: undefined as unknown as {},\n \
response: undefined as unknown as {}\n\
}}
",
Self::name(),
Self::ENDPOINT,
#path_params,
#path_params,
Self::METHOD,
#call_name_req,
#call_name_res

View File

@ -9,10 +9,10 @@ use ts_rs::TS;
#[derive(Serialize, Deserialize, TS)]
#[ts(export)]
pub struct NoteByIdReq {
#[serde(default)]
pub context: bool,
#[serde(default)]
pub attachments: bool,
#[ts(optional)]
pub context: Option<bool>,
#[ts(optional)]
pub attachments: Option<bool>,
}
#[derive(Endpoint)]

View File

@ -10,13 +10,14 @@ use ts_rs::TS;
#[derive(Serialize, Deserialize, TS)]
#[ts(export)]
pub struct GetTimelineReq {
#[serde(default = "default_timeline_limit")]
pub limit: U64Range<1, 100>,
#[ts(optional)]
pub limit: Option<U64Range<1, 100>>,
#[ts(optional)]
pub filter: Option<NoteListFilter>,
}
fn default_timeline_limit<const MIN: u64, const MAX: u64>() -> U64Range<MIN, MAX> {
15.try_into().unwrap()
pub fn default_timeline_limit<const MIN: u64, const MAX: u64>() -> U64Range<MIN, MAX> {
30.try_into().unwrap()
}
#[derive(Endpoint)]

View File

@ -10,14 +10,14 @@ use crate::types::user::{PackUserMaybeAll, PackUserSelfMaybeAll};
#[derive(Serialize, Deserialize, TS)]
#[ts(export)]
pub struct UserSelfReq {
#[serde(default)]
pub profile: bool,
#[serde(default)]
pub pins: bool,
#[serde(default)]
pub detail: bool,
#[serde(default)]
pub secrets: bool,
#[ts(optional)]
pub profile: Option<bool>,
#[ts(optional)]
pub pins: Option<bool>,
#[ts(optional)]
pub detail: Option<bool>,
#[ts(optional)]
pub secrets: Option<bool>,
}
#[derive(Endpoint)]
@ -33,16 +33,16 @@ pub struct GetUserSelf;
#[derive(Serialize, Deserialize, TS)]
#[ts(export)]
pub struct UserByIdReq {
#[serde(default)]
pub profile: bool,
#[serde(default)]
pub pins: bool,
#[serde(default)]
pub detail: bool,
#[serde(default)]
pub relation: bool,
#[serde(default)]
pub auth: bool,
#[ts(optional)]
pub profile: Option<bool>,
#[ts(optional)]
pub pins: Option<bool>,
#[ts(optional)]
pub detail: Option<bool>,
#[ts(optional)]
pub relation: Option<bool>,
#[ts(optional)]
pub auth: Option<bool>,
}
#[derive(Endpoint)]

View File

@ -22,8 +22,8 @@ pub async fn handle_note(
) -> Result<Json<Res<GetNoteById>>, ApiError> {
let ctx = PackingContext::new(service, self_user.clone()).await?;
let note = NoteModel {
attachments,
with_context: context,
attachments: attachments.unwrap_or_default(),
with_context: context.unwrap_or_default(),
}
.fetch_single(&ctx, self_user.as_deref(), &id)
.await?