TS_RS Rust and TypeScript types should now be in parity
This commit is contained in:
parent
c8627a996c
commit
a5ab2acca0
|
@ -1,12 +1,16 @@
|
|||
import {FrontendApiEndpoint, FrontendApiEndpoints} from "./fe-api";
|
||||
import {GetNoteById} from "./types/endpoints/GetNoteById";
|
||||
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") {
|
||||
|
@ -84,7 +103,7 @@ export class MagApiClient {
|
|||
body: method !== "GET" ? JSON.stringify(data) : undefined,
|
||||
credentials: "omit",
|
||||
cache: "no-cache",
|
||||
headers: authorization ? {authorization} : {},
|
||||
headers: authorization ? { authorization } : {},
|
||||
})
|
||||
.then(async (res) => {
|
||||
const body = res.status === 204 ? null : await res.json();
|
||||
|
@ -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" });
|
||||
|
|
|
@ -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, }
|
|
@ -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, }
|
|
@ -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, }
|
|
@ -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, }
|
|
@ -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, }
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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>,
|
||||
};
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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?
|
||||
|
|
Loading…
Reference in New Issue