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";
|
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;
|
method: M;
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
pathParams: [string],
|
pathParams: PP;
|
||||||
request?: T;
|
request?: T;
|
||||||
response?: R;
|
response?: R;
|
||||||
}
|
}
|
||||||
|
@ -18,21 +22,22 @@ function nestedUrlSearchParams(data: any, topLevel: boolean = true): string {
|
||||||
case "boolean":
|
case "boolean":
|
||||||
case "number":
|
case "number":
|
||||||
case "symbol":
|
case "symbol":
|
||||||
if (topLevel)
|
if (topLevel) return encodeURIComponent(data.toString()) + "=";
|
||||||
return encodeURIComponent(data.toString()) + "=";
|
|
||||||
|
|
||||||
return data.toString();
|
return data.toString();
|
||||||
case "object":
|
case "object":
|
||||||
if (data === null)
|
if (data === null) return "null";
|
||||||
return "null";
|
|
||||||
|
|
||||||
if (Array.isArray(data))
|
if (Array.isArray(data))
|
||||||
return data.map(d => nestedUrlSearchParams(d, true))
|
return data
|
||||||
|
.map((d) => nestedUrlSearchParams(d, true))
|
||||||
.map(encodeURIComponent)
|
.map(encodeURIComponent)
|
||||||
.join("&");
|
.join("&");
|
||||||
|
|
||||||
const inner = Object.entries(data)
|
const inner = Object.entries(data).map(([k, v]) => [
|
||||||
.map(([k, v]) => [k, nestedUrlSearchParams(v, false)]);
|
k,
|
||||||
|
nestedUrlSearchParams(v, false),
|
||||||
|
]);
|
||||||
|
|
||||||
return new URLSearchParams(inner).toString();
|
return new URLSearchParams(inner).toString();
|
||||||
|
|
||||||
|
@ -45,8 +50,8 @@ type MagApiErrorCode = "Client:GenericApiError" | string;
|
||||||
|
|
||||||
export interface MagApiError {
|
export interface MagApiError {
|
||||||
status: number;
|
status: number;
|
||||||
code: MagApiErrorCode,
|
code: MagApiErrorCode;
|
||||||
message: string,
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MagApiClient {
|
export class MagApiClient {
|
||||||
|
@ -56,11 +61,21 @@ export class MagApiClient {
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
async call<M extends Method, T extends BackendApiEndpoint<M, T["request"], T["response"]>>(
|
async call<
|
||||||
endpoint: T["endpoint"],
|
T extends BackendApiEndpoint<
|
||||||
method: M,
|
T["method"] & Method,
|
||||||
|
T["pathParams"] & string[],
|
||||||
|
T["request"],
|
||||||
|
T["response"]
|
||||||
|
>
|
||||||
|
>(
|
||||||
|
{ endpoint, method }: T,
|
||||||
data: T["request"],
|
data: T["request"],
|
||||||
pathParams: Record<string, string>,
|
pathParams: {
|
||||||
|
[K in keyof T["pathParams"] as T["pathParams"][K] & string]:
|
||||||
|
| string
|
||||||
|
| number;
|
||||||
|
},
|
||||||
token?: string | null | undefined
|
token?: string | null | undefined
|
||||||
): Promise<T["response"]> {
|
): Promise<T["response"]> {
|
||||||
type Response = T["response"];
|
type Response = T["response"];
|
||||||
|
@ -70,6 +85,10 @@ export class MagApiClient {
|
||||||
? `Bearer ${authorizationToken}`
|
? `Bearer ${authorizationToken}`
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
for (const name in pathParams) {
|
||||||
|
endpoint = endpoint.replace(`:${name}`, `${pathParams[name]}`);
|
||||||
|
}
|
||||||
|
|
||||||
let url = `${this.baseUrl}/${endpoint}`;
|
let url = `${this.baseUrl}/${endpoint}`;
|
||||||
|
|
||||||
if (method === "GET") {
|
if (method === "GET") {
|
||||||
|
@ -84,7 +103,7 @@ export class MagApiClient {
|
||||||
body: method !== "GET" ? JSON.stringify(data) : undefined,
|
body: method !== "GET" ? JSON.stringify(data) : undefined,
|
||||||
credentials: "omit",
|
credentials: "omit",
|
||||||
cache: "no-cache",
|
cache: "no-cache",
|
||||||
headers: authorization ? {authorization} : {},
|
headers: authorization ? { authorization } : {},
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
const body = res.status === 204 ? null : await res.json();
|
const body = res.status === 204 ? null : await res.json();
|
||||||
|
@ -98,15 +117,14 @@ export class MagApiClient {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
throw ({
|
throw {
|
||||||
status: -1,
|
status: -1,
|
||||||
code: "Client:GenericApiError",
|
code: "Client:GenericApiError",
|
||||||
message: e
|
message: e,
|
||||||
}) as MagApiError;
|
} as MagApiError;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const a = new MagApiClient("https://aaa");
|
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.
|
// 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";
|
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.
|
// 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.
|
// 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.
|
// 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 { NoteByIdReq } from "../NoteByIdReq";
|
||||||
import type { PackNoteMaybeFull } from "../packed/PackNoteMaybeFull";
|
import type { PackNoteMaybeFull } from "../packed/PackNoteMaybeFull";
|
||||||
|
|
||||||
interface GetNoteById {
|
export const GetNoteById = {
|
||||||
endpoint: "/notes/:id",
|
endpoint: "/notes/:id",
|
||||||
pathParams: ["id"],
|
pathParams: ["id"] as ["id"],
|
||||||
method: "GET",
|
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||||
request: NoteByIdReq,
|
request: undefined as unknown as NoteByIdReq,
|
||||||
response: PackNoteMaybeFull
|
response: undefined as unknown as PackNoteMaybeFull
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,10 @@
|
||||||
import type { GetTimelineReq } from "../GetTimelineReq";
|
import type { GetTimelineReq } from "../GetTimelineReq";
|
||||||
import type { PackNoteMaybeFull } from "../packed/PackNoteMaybeFull";
|
import type { PackNoteMaybeFull } from "../packed/PackNoteMaybeFull";
|
||||||
|
|
||||||
export interface GetTimeline {
|
export const GetTimeline = {
|
||||||
endpoint: "/timeline";
|
endpoint: "/timeline",
|
||||||
pathParams: [];
|
pathParams: [] as [],
|
||||||
method: "GET";
|
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||||
request: GetTimelineReq;
|
request: undefined as unknown as GetTimelineReq,
|
||||||
response: Array<PackNoteMaybeFull>;
|
response: undefined as unknown as Array<PackNoteMaybeFull>,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
||||||
import type { UserByIdReq } from "../UserByIdReq";
|
import type { UserByIdReq } from "../UserByIdReq";
|
||||||
|
|
||||||
export interface GetUserByAcct {
|
export const GetUserByAcct = {
|
||||||
endpoint: "/users/by-acct/:user_id";
|
endpoint: "/users/by-acct/:user_id",
|
||||||
pathParams: ["user_id"];
|
pathParams: ["user_id"] as ["user_id"],
|
||||||
method: "GET";
|
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||||
request: UserByIdReq;
|
request: undefined as unknown as UserByIdReq,
|
||||||
response: PackUserMaybeAll;
|
response: undefined as unknown as PackUserMaybeAll
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
||||||
import type { UserByIdReq } from "../UserByIdReq";
|
import type { UserByIdReq } from "../UserByIdReq";
|
||||||
|
|
||||||
export interface GetUserById {
|
export const GetUserById = {
|
||||||
endpoint: "/users/:user_id";
|
endpoint: "/users/:user_id",
|
||||||
pathParams: ["user_id"];
|
pathParams: ["user_id"] as ["user_id"],
|
||||||
method: "GET";
|
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||||
request: UserByIdReq;
|
request: undefined as unknown as UserByIdReq,
|
||||||
response: PackUserMaybeAll;
|
response: undefined as unknown as PackUserMaybeAll
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
import type { PackUserSelfMaybeAll } from "../packed/PackUserSelfMaybeAll";
|
import type { PackUserSelfMaybeAll } from "../packed/PackUserSelfMaybeAll";
|
||||||
import type { UserSelfReq } from "../UserSelfReq";
|
import type { UserSelfReq } from "../UserSelfReq";
|
||||||
|
|
||||||
export interface GetUserSelf {
|
export const GetUserSelf = {
|
||||||
endpoint: "/users/@self";
|
endpoint: "/users/@self",
|
||||||
pathParams: [];
|
pathParams: [] as [],
|
||||||
method: "GET";
|
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||||
request: UserSelfReq;
|
request: undefined as unknown as UserSelfReq,
|
||||||
response: PackUserSelfMaybeAll;
|
response: undefined as unknown as PackUserSelfMaybeAll
|
||||||
}
|
}
|
||||||
|
|
|
@ -356,17 +356,18 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
fn decl() -> String {
|
fn decl() -> String {
|
||||||
format!(
|
format!(
|
||||||
"interface {} {{\n \
|
"const {} = {{\n \
|
||||||
endpoint: \"{}\";\n \
|
endpoint: \"{}\",\n \
|
||||||
pathParams: {};\n \
|
pathParams: {} as {},\n \
|
||||||
method: \"{}\";\n \
|
method: \"{}\" as \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\",\n \
|
||||||
request: {};\n \
|
request: undefined as unknown as {},\n \
|
||||||
response: {};\n\
|
response: undefined as unknown as {}\n\
|
||||||
}}
|
}}
|
||||||
",
|
",
|
||||||
Self::name(),
|
Self::name(),
|
||||||
Self::ENDPOINT,
|
Self::ENDPOINT,
|
||||||
#path_params,
|
#path_params,
|
||||||
|
#path_params,
|
||||||
Self::METHOD,
|
Self::METHOD,
|
||||||
#call_name_req,
|
#call_name_req,
|
||||||
#call_name_res
|
#call_name_res
|
||||||
|
|
|
@ -9,10 +9,10 @@ use ts_rs::TS;
|
||||||
#[derive(Serialize, Deserialize, TS)]
|
#[derive(Serialize, Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct NoteByIdReq {
|
pub struct NoteByIdReq {
|
||||||
#[serde(default)]
|
#[ts(optional)]
|
||||||
pub context: bool,
|
pub context: Option<bool>,
|
||||||
#[serde(default)]
|
#[ts(optional)]
|
||||||
pub attachments: bool,
|
pub attachments: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[derive(Endpoint)]
|
||||||
|
|
|
@ -10,13 +10,14 @@ use ts_rs::TS;
|
||||||
#[derive(Serialize, Deserialize, TS)]
|
#[derive(Serialize, Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct GetTimelineReq {
|
pub struct GetTimelineReq {
|
||||||
#[serde(default = "default_timeline_limit")]
|
#[ts(optional)]
|
||||||
pub limit: U64Range<1, 100>,
|
pub limit: Option<U64Range<1, 100>>,
|
||||||
|
#[ts(optional)]
|
||||||
pub filter: Option<NoteListFilter>,
|
pub filter: Option<NoteListFilter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_timeline_limit<const MIN: u64, const MAX: u64>() -> U64Range<MIN, MAX> {
|
pub fn default_timeline_limit<const MIN: u64, const MAX: u64>() -> U64Range<MIN, MAX> {
|
||||||
15.try_into().unwrap()
|
30.try_into().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[derive(Endpoint)]
|
||||||
|
|
|
@ -10,14 +10,14 @@ use crate::types::user::{PackUserMaybeAll, PackUserSelfMaybeAll};
|
||||||
#[derive(Serialize, Deserialize, TS)]
|
#[derive(Serialize, Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct UserSelfReq {
|
pub struct UserSelfReq {
|
||||||
#[serde(default)]
|
#[ts(optional)]
|
||||||
pub profile: bool,
|
pub profile: Option<bool>,
|
||||||
#[serde(default)]
|
#[ts(optional)]
|
||||||
pub pins: bool,
|
pub pins: Option<bool>,
|
||||||
#[serde(default)]
|
#[ts(optional)]
|
||||||
pub detail: bool,
|
pub detail: Option<bool>,
|
||||||
#[serde(default)]
|
#[ts(optional)]
|
||||||
pub secrets: bool,
|
pub secrets: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[derive(Endpoint)]
|
||||||
|
@ -33,16 +33,16 @@ pub struct GetUserSelf;
|
||||||
#[derive(Serialize, Deserialize, TS)]
|
#[derive(Serialize, Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct UserByIdReq {
|
pub struct UserByIdReq {
|
||||||
#[serde(default)]
|
#[ts(optional)]
|
||||||
pub profile: bool,
|
pub profile: Option<bool>,
|
||||||
#[serde(default)]
|
#[ts(optional)]
|
||||||
pub pins: bool,
|
pub pins: Option<bool>,
|
||||||
#[serde(default)]
|
#[ts(optional)]
|
||||||
pub detail: bool,
|
pub detail: Option<bool>,
|
||||||
#[serde(default)]
|
#[ts(optional)]
|
||||||
pub relation: bool,
|
pub relation: Option<bool>,
|
||||||
#[serde(default)]
|
#[ts(optional)]
|
||||||
pub auth: bool,
|
pub auth: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[derive(Endpoint)]
|
||||||
|
|
|
@ -22,8 +22,8 @@ pub async fn handle_note(
|
||||||
) -> Result<Json<Res<GetNoteById>>, ApiError> {
|
) -> Result<Json<Res<GetNoteById>>, ApiError> {
|
||||||
let ctx = PackingContext::new(service, self_user.clone()).await?;
|
let ctx = PackingContext::new(service, self_user.clone()).await?;
|
||||||
let note = NoteModel {
|
let note = NoteModel {
|
||||||
attachments,
|
attachments: attachments.unwrap_or_default(),
|
||||||
with_context: context,
|
with_context: context.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
.fetch_single(&ctx, self_user.as_deref(), &id)
|
.fetch_single(&ctx, self_user.as_deref(), &id)
|
||||||
.await?
|
.await?
|
||||||
|
|
Loading…
Reference in New Issue