1
1
import omit from 'lodash/omit' ;
2
2
import pick from 'lodash/pick' ;
3
- import { SafeReturn , trySafe } from 'p-safe' ;
3
+ import { trySafe , type SafeReturn } from 'p-safe' ;
4
4
import { z } from 'zod' ;
5
- import { generateRequest , ZodRequestInit , ZodResponse } from 'zod-request' ;
5
+ import {
6
+ generateRequest ,
7
+ ZodResponse ,
8
+ type ZodRequestInit ,
9
+ type ZodValidationError
10
+ } from 'zod-request' ;
6
11
7
12
import { VaultError } from '@/errors' ;
8
- import { CommandFn , CommandInit , RequestSchema } from '@/typings' ;
13
+ import type { CommandFn , CommandInit , RequestSchema } from '@/typings' ;
9
14
10
15
import { isJson } from './json' ;
11
16
import { removeUndefined } from './object' ;
@@ -16,45 +21,54 @@ export function generateCommand<Schema extends RequestSchema, RawResponse extend
16
21
raw : RawResponse = false
17
22
) : CommandFn < Schema , RawResponse > {
18
23
return async ( args , options = { } ) => {
19
- return trySafe ( async ( ) => {
20
- const { method = 'GET' , path, client, schema } = init ;
21
- const { strictSchema = true , ...opts } = options ;
22
-
23
- const { url : _url , input } = generateRequest (
24
- `${ client . endpoint } /${ client . apiVersion } ${ client . pathPrefix } ${ path } ` ,
25
- {
26
- method,
27
- ...opts ,
28
- path : ! schema ?. path ? undefined : pick ( args || { } , Object . keys ( schema . path . shape ) ) ,
29
- params : ! schema ?. searchParams
30
- ? undefined
31
- : pick ( args || { } , Object . keys ( schema . searchParams . shape ) ) ,
32
- body : ! schema ?. body
33
- ? undefined
34
- : schema . body instanceof z . ZodObject
35
- ? pick ( args || { } , Object . keys ( schema . body . shape ) )
36
- : ( removeUndefined (
37
- omit (
38
- args ,
39
- // Potential Body Keys
40
- Object . keys ( schema . searchParams ?. shape || { } )
41
- . concat ( Object . keys ( schema . path ?. shape || { } ) )
42
- . concat ( Object . keys ( schema . headers ?. shape || { } ) )
43
- )
44
- ) as any ) ,
45
- headers : removeUndefined (
46
- Object . assign (
47
- {
48
- 'X-Vault-Token' : client . token ,
49
- 'X-Vault-Namespace' : client . namespace
50
- } ,
51
- opts . headers || { }
52
- )
53
- ) ,
54
- schema
55
- } as ZodRequestInit < any , any >
56
- ) ;
24
+ const { method = 'GET' , path, client, schema } = init ;
25
+ const { strictSchema = true , ...opts } = options ;
26
+
27
+ const requestInit = {
28
+ method,
29
+ ...opts ,
30
+ path : ! schema ?. path ? undefined : pick ( args || { } , Object . keys ( schema . path . shape ) ) ,
31
+ params : ! schema ?. searchParams
32
+ ? undefined
33
+ : pick ( args || { } , Object . keys ( schema . searchParams . shape ) ) ,
34
+ body : ! schema ?. body
35
+ ? undefined
36
+ : schema . body instanceof z . ZodObject
37
+ ? pick ( args || { } , Object . keys ( schema . body . shape ) )
38
+ : ( removeUndefined (
39
+ omit (
40
+ args ,
41
+ // Potential Body Keys
42
+ Object . keys ( schema . searchParams ?. shape || { } )
43
+ . concat ( Object . keys ( schema . path ?. shape || { } ) )
44
+ . concat ( Object . keys ( schema . headers ?. shape || { } ) )
45
+ )
46
+ ) as any ) ,
47
+ headers : removeUndefined (
48
+ Object . assign (
49
+ {
50
+ 'X-Vault-Token' : client . token ,
51
+ 'X-Vault-Namespace' : client . namespace
52
+ } ,
53
+ opts . headers || { }
54
+ )
55
+ ) ,
56
+ schema : Object . assign ( schema , {
57
+ response : z . union ( [
58
+ schema . response ?? z . any ( ) ,
59
+ z . object ( {
60
+ errors : z . array ( z . string ( ) )
61
+ } )
62
+ ] )
63
+ } )
64
+ } as ZodRequestInit < any , any > ;
65
+
66
+ const { url : _url , input } = generateRequest (
67
+ `${ client . endpoint } /${ client . apiVersion } ${ client . pathPrefix } ${ path } ` ,
68
+ requestInit
69
+ ) ;
57
70
71
+ return trySafe ( async ( ) => {
58
72
const fetcher = init . fetcher || client . fetcher || fetch ;
59
73
60
74
const rawInit = Object . assign ( input as RequestInit , {
@@ -90,30 +104,47 @@ export function generateCommand<Schema extends RequestSchema, RawResponse extend
90
104
91
105
if ( ! strictSchema || ! schema . response || schema . response instanceof z . ZodAny ) {
92
106
if ( hasJsonContentType ) {
93
- return resolve ( await response . json ( ) ) ;
107
+ return resolve ( response , await response . json ( ) ) ;
94
108
}
95
109
96
- return resolve ( parseText ( await response . text ( ) ) ) ;
110
+ return resolve ( response , parseText ( await response . text ( ) ) ) ;
97
111
}
98
112
99
- const zr = new ZodResponse ( response , schema . response ) ;
113
+ // From here it might throw a schema validation error
114
+ try {
115
+ const zr = new ZodResponse ( response , schema . response ) ;
100
116
101
- if ( hasJsonContentType ) {
102
- return resolve ( await zr . json ( ) ) ;
103
- }
117
+ if ( hasJsonContentType ) {
118
+ return resolve ( response , await zr . json ( ) ) ;
119
+ }
120
+
121
+ return resolve ( response , parseText ( await zr . text ( ) ) ) ;
122
+ } catch ( e ) {
123
+ if ( e && e instanceof VaultError ) return { error : e } ;
104
124
105
- return resolve ( parseText ( await zr . text ( ) ) ) ;
125
+ if ( e && typeof e === 'object' && e . constructor . name === 'ZodValidationError' ) {
126
+ const error = new VaultError ( 'Failed to validate response schema' ) ;
127
+ error . cause = ( e as unknown as ZodValidationError ) . flatten ( ) ;
128
+ return { error } ;
129
+ }
130
+
131
+ const error = new VaultError ( 'Failed to parse response' ) ;
132
+ error . cause = e ;
133
+ return { error } ;
134
+ }
106
135
} ) ;
107
136
} ;
108
137
}
109
138
110
- function resolve < T > ( response : any ) : SafeReturn < T , VaultError > {
139
+ function resolve < T > ( response : Response , data : any ) : SafeReturn < T , VaultError > {
111
140
// It's a Json error response
112
- if ( typeof response === 'object' && 'errors' in response ) {
113
- return { error : new VaultError ( response . errors ) } ;
141
+ if ( typeof data === 'object' && 'errors' in data ) {
142
+ const error = new VaultError ( data . errors ) ;
143
+ error . cause = response ;
144
+ return { error } ;
114
145
}
115
146
116
- return { data : response } ;
147
+ return { data } ;
117
148
}
118
149
119
150
function parseText ( text : string ) : object | string {
0 commit comments