@@ -523,69 +523,6 @@ describe("generator", () => {
523
523
} ,
524
524
} ,
525
525
"paths" : {
526
- // "/authentication/refresh": {
527
- // "post": {
528
- // "summary": "Refresh authentication tokens",
529
- // "tags": [
530
- // "Authentication"
531
- // ],
532
- // "requestBody": {
533
- // "content": {
534
- // "application/json": {
535
- // "schema": {
536
- // "type": "object",
537
- // "properties": {
538
- // "accessToken": {
539
- // "type": "string",
540
- // "pattern": "^\\w+ [A-Za-z0-9-_=]+\\.[A-Za-z0-9-_=]+\\.?[A-Za-z0-9-_.+/=]*$"
541
- // },
542
- // "refreshToken": {
543
- // "type": "string",
544
- // "format": "uuid"
545
- // }
546
- // },
547
- // "required": [
548
- // "accessToken",
549
- // "refreshToken"
550
- // ]
551
- // }
552
- // }
553
- // }
554
- // },
555
- // "responses": {
556
- // "200": {
557
- // "description": "Refresh the access and refresh tokens",
558
- // "content": {
559
- // "application/json": {
560
- // "schema": {
561
- // "$ref": "#/components/schemas/SerializedUserSession"
562
- // }
563
- // }
564
- // }
565
- // },
566
- // "401": {
567
- // "description": "Unauthorized",
568
- // "content": {
569
- // "application/json": {
570
- // "schema": {
571
- // "type": "string"
572
- // }
573
- // }
574
- // }
575
- // },
576
- // "500": {
577
- // "description": "Internal server error",
578
- // "content": {
579
- // "application/json": {
580
- // "schema": {
581
- // "type": "string"
582
- // }
583
- // }
584
- // }
585
- // }
586
- // }
587
- // }
588
- // },
589
526
"/authorization/organizations/:organizationId/members/search" : {
590
527
"get" : {
591
528
"summary" : "Search for members in an organization" ,
@@ -878,4 +815,214 @@ describe("generator", () => {
878
815
"
879
816
` ) ;
880
817
} ) ;
818
+
819
+ test . only ( "optional query param" , async ( { expect } ) => {
820
+ expect ( await prettify ( generateFile ( mapOpenApiEndpoints ( {
821
+ "openapi" : "3.0.0" ,
822
+ "info" : {
823
+ "version" : "1.0.0" ,
824
+ "title" : "Demo API"
825
+ } ,
826
+ "paths" : {
827
+ "/demo" : {
828
+ "get" : {
829
+ "parameters" : [
830
+ {
831
+ "schema" : {
832
+ "type" : "string" ,
833
+ "format" : "uuid"
834
+ } ,
835
+ "required" : true ,
836
+ "name" : "organizationId" ,
837
+ "in" : "query"
838
+ } ,
839
+ {
840
+ "schema" : {
841
+ "type" : "string"
842
+ } ,
843
+ "required" : false ,
844
+ "name" : "searchQuery" ,
845
+ "in" : "query"
846
+ } ,
847
+ {
848
+ "schema" : {
849
+ "type" : "string" ,
850
+ "format" : "uuid"
851
+ } ,
852
+ "required" : false ,
853
+ "name" : "optionalInPath1" ,
854
+ "in" : "path"
855
+ } ,
856
+ {
857
+ "schema" : {
858
+ "type" : "string"
859
+ } ,
860
+ "required" : false ,
861
+ "name" : "optionalInPath2" ,
862
+ "in" : "path"
863
+ } ,
864
+ ] ,
865
+ "responses" : {
866
+ "200" : {
867
+ "content" : {
868
+ "application/json" : {
869
+ "schema" : {
870
+ "type" : "string" ,
871
+ }
872
+ }
873
+ }
874
+ }
875
+ }
876
+ }
877
+ } ,
878
+ }
879
+ } ) ) ) ) . toMatchInlineSnapshot ( `
880
+ "export namespace Schemas {
881
+ // <Schemas>
882
+ // </Schemas>
883
+ }
884
+
885
+ export namespace Endpoints {
886
+ // <Endpoints>
887
+
888
+ export type get__demo = {
889
+ method: "GET";
890
+ path: "/demo";
891
+ requestFormat: "json";
892
+ parameters: {
893
+ query: { organizationId: string; searchQuery: string | undefined };
894
+ path: Partial<{ optionalInPath1: string; optionalInPath2: string }>;
895
+ };
896
+ response: string;
897
+ };
898
+
899
+ // </Endpoints>
900
+ }
901
+
902
+ // <EndpointByMethod>
903
+ export type EndpointByMethod = {
904
+ get: {
905
+ "/demo": Endpoints.get__demo;
906
+ };
907
+ };
908
+
909
+ // </EndpointByMethod>
910
+
911
+ // <EndpointByMethod.Shorthands>
912
+ export type GetEndpoints = EndpointByMethod["get"];
913
+ // </EndpointByMethod.Shorthands>
914
+
915
+ // <ApiClientTypes>
916
+ export type EndpointParameters = {
917
+ body?: unknown;
918
+ query?: Record<string, unknown>;
919
+ header?: Record<string, unknown>;
920
+ path?: Record<string, unknown>;
921
+ };
922
+
923
+ export type MutationMethod = "post" | "put" | "patch" | "delete";
924
+ export type Method = "get" | "head" | "options" | MutationMethod;
925
+
926
+ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text";
927
+
928
+ export type DefaultEndpoint = {
929
+ parameters?: EndpointParameters | undefined;
930
+ response: unknown;
931
+ };
932
+
933
+ export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
934
+ operationId: string;
935
+ method: Method;
936
+ path: string;
937
+ requestFormat: RequestFormat;
938
+ parameters?: TConfig["parameters"];
939
+ meta: {
940
+ alias: string;
941
+ hasParameters: boolean;
942
+ areParametersRequired: boolean;
943
+ };
944
+ response: TConfig["response"];
945
+ };
946
+
947
+ export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise<Response>;
948
+
949
+ type RequiredKeys<T> = {
950
+ [P in keyof T]-?: undefined extends T[P] ? never : P;
951
+ }[keyof T];
952
+
953
+ type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [config: T];
954
+
955
+ // </ApiClientTypes>
956
+
957
+ // <ApiClient>
958
+ export class ApiClient {
959
+ baseUrl: string = "";
960
+
961
+ constructor(public fetcher: Fetcher) {}
962
+
963
+ setBaseUrl(baseUrl: string) {
964
+ this.baseUrl = baseUrl;
965
+ return this;
966
+ }
967
+
968
+ parseResponse = async <T,>(response: Response): Promise<T> => {
969
+ const contentType = response.headers.get("content-type");
970
+ if (contentType?.includes("application/json")) {
971
+ return response.json();
972
+ }
973
+ return response.text() as unknown as T;
974
+ };
975
+
976
+ // <ApiClient.get>
977
+ get<Path extends keyof GetEndpoints, TEndpoint extends GetEndpoints[Path]>(
978
+ path: Path,
979
+ ...params: MaybeOptionalArg<TEndpoint["parameters"]>
980
+ ): Promise<TEndpoint["response"]> {
981
+ return this.fetcher("get", this.baseUrl + path, params[0]).then((response) =>
982
+ this.parseResponse(response),
983
+ ) as Promise<TEndpoint["response"]>;
984
+ }
985
+ // </ApiClient.get>
986
+
987
+ // <ApiClient.request>
988
+ /**
989
+ * Generic request method with full type-safety for any endpoint
990
+ */
991
+ request<
992
+ TMethod extends keyof EndpointByMethod,
993
+ TPath extends keyof EndpointByMethod[TMethod],
994
+ TEndpoint extends EndpointByMethod[TMethod][TPath],
995
+ >(
996
+ method: TMethod,
997
+ path: TPath,
998
+ ...params: MaybeOptionalArg<TEndpoint extends { parameters: infer Params } ? Params : never>
999
+ ): Promise<
1000
+ Omit<Response, "json"> & {
1001
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */
1002
+ json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
1003
+ }
1004
+ > {
1005
+ return this.fetcher(method, this.baseUrl + (path as string), params[0] as EndpointParameters);
1006
+ }
1007
+ // </ApiClient.request>
1008
+ }
1009
+
1010
+ export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
1011
+ return new ApiClient(fetcher).setBaseUrl(baseUrl ?? "");
1012
+ }
1013
+
1014
+ /**
1015
+ Example usage:
1016
+ const api = createApiClient((method, url, params) =>
1017
+ fetch(url, { method, body: JSON.stringify(params) }).then((res) => res.json()),
1018
+ );
1019
+ api.get("/users").then((users) => console.log(users));
1020
+ api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
1021
+ api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
1022
+ */
1023
+
1024
+ // </ApiClient
1025
+ "
1026
+ ` ) ;
1027
+ } ) ;
881
1028
} ) ;
0 commit comments