Skip to content

Commit 0620a22

Browse files
committed
feat: adding Cqrs.sid|.author to base; feat: adding Api.InitOptions.headers() predicate; fix: api request error
1 parent e1a7b14 commit 0620a22

5 files changed

Lines changed: 94 additions & 21 deletions

File tree

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@danielfroz/sloth",
3-
"version": "0.1.21",
3+
"version": "0.1.22",
44
"description": "Core module for the Sloth framework",
55
"license": "MIT",
66
"exports": {

src/Api.ts

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,54 @@
11
import { Errors } from "./mod.ts";
22

3+
type HeadersPredicate = (body?: unknown) => Record<string, string>
4+
35
export interface ApiInitOptions {
4-
base?: string
56
throwOnError: boolean
7+
base?: string
8+
/**
9+
* Allows to add custom header generation logic based on request body.
10+
* Useful for middleware processing; as body is not necessary for headers parsing
11+
*
12+
* @example
13+
* ```ts
14+
* const api = new ApiFetch().init({
15+
* base: 'https://base_service_com/api',
16+
* throwOnError: true,
17+
* headers: function (body: unknown) {
18+
* const { authorId, correlationId } = body as { authorId: string, correlationId: string }
19+
* return {
20+
* 'X-Author-ID': authorId,
21+
* 'X-Correlation-ID': correlationId
22+
* }
23+
* },
24+
* })
25+
* ```
26+
*/
27+
headers?: HeadersPredicate
628
}
729

830
export interface ApiGetOptions {
31+
/**
32+
* Request url. Url can be absolute or relative
33+
*
34+
* @example Relative request
35+
* ```ts
36+
* const { user } = api.get<Cqrs.QueryResult>({ url: `/user/${user.id}` })
37+
* ```
38+
*
39+
* @example Absolute request
40+
* ```ts
41+
* const { user } = api.get<Cqrs.QueryResult>({
42+
* url: 'https://www.example.com/api/user/get',
43+
* })
44+
* ```
45+
*/
946
url: string
10-
headers?: Record<string, string>
47+
/**
48+
* Headers or HeadersPredicate.
49+
* For headers predicate, @see ApiInitOptions.headers
50+
*/
51+
headers?: Record<string, string>|HeadersPredicate
1152
}
1253

1354
export interface ApiPostOptions extends ApiGetOptions {
@@ -26,9 +67,9 @@ export class ApiFetch implements Api {
2667
init(options?: ApiInitOptions): Api {
2768
let base = options?.base
2869
if(base && base.endsWith('/')) {
29-
base = base.substring(0, base.length - 2)
70+
base = base.substring(0, base.length - 1)
3071
}
31-
this.options = { base, throwOnError: options?.throwOnError ?? true }
72+
this.options = { base, throwOnError: options?.throwOnError ?? true, headers: options?.headers }
3273
return this
3374
}
3475

@@ -46,29 +87,44 @@ export class ApiFetch implements Api {
4687
}
4788

4889
get<R extends object>(options: ApiGetOptions): Promise<R> {
90+
const initOptions = this.options
4991
return new Promise(async (resolve, reject) => {
5092
if(!options.url)
5193
throw new Errors.ArgumentError('url')
5294

5395
const url = this._url(options.url)
54-
if(!options.headers) {
55-
options.headers = {} as Record<string,string>
96+
const headers = {} as Record<string,string>
97+
if(options.headers) {
98+
let records = typeof(options.headers) === 'function' ? options.headers(): options.headers
99+
if(records) {
100+
for(const [ key, value ] of Object.entries(records)) {
101+
headers[key] = value
102+
}
103+
}
104+
}
105+
if(initOptions?.headers) {
106+
const records = initOptions.headers()
107+
if(records) {
108+
for(const [ key, value ] of Object.entries(records)) {
109+
headers[key] = value
110+
}
111+
}
56112
}
57-
options.headers['Accept'] = 'application/json'
58-
options.headers['Content-Type'] = 'application/json'
113+
headers['Accept'] = 'application/json'
114+
headers['Content-Type'] = 'application/json'
59115
try {
60116
const res = await fetch(url, {
61117
method: 'GET',
62-
headers: options.headers,
118+
headers,
63119
})
64120
const json = await res.json()
65121
if(res.status !== 200 && res.status !== 201) {
66122
if(json.error?.code) {
67-
throw new Errors.ApiError('POST', url, res.status, json.error?.code, json.error?.message)
123+
throw new Errors.ApiError('GET', url, res.status, json.error?.code, json.error?.message)
68124
}
69125
else {
70126
// generic error code
71-
throw new Errors.ApiError('POST', url, res.status, 'service', JSON.stringify(json))
127+
throw new Errors.ApiError('GET', url, res.status, 'service', JSON.stringify(json))
72128
}
73129
}
74130
return resolve(json as R)
@@ -77,30 +133,44 @@ export class ApiFetch implements Api {
77133
if(error.code)
78134
return reject(error)
79135
// generic error
80-
return reject(new Errors.ApiError('POST', url, 500, 'service', error.message))
136+
return reject(new Errors.ApiError('GET', url, 500, 'service', error.message))
81137
}
82138
})
83139
}
84140

85141
post<R extends object>(options: ApiPostOptions): Promise<R> {
142+
const initOptions = this.options
86143
return new Promise(async (resolve, reject) => {
87144
if(!options.url)
88145
throw new Errors.ArgumentError('url')
89146
if(!options.body)
90147
throw new Errors.ArgumentError('body')
91148

92149
const url = this._url(options.url)
93-
if(!options.headers) {
94-
options.headers = {} as Record<string,string>
150+
const headers = {} as Record<string,string>
151+
if(options.headers) {
152+
const records = typeof(options.headers) === 'function' ? options.headers(): options.headers;
153+
if(records) {
154+
for(const [ key, value ] of Object.entries(records)) {
155+
headers[key] = value
156+
}
157+
}
158+
}
159+
if(initOptions?.headers) {
160+
const records = initOptions.headers(options.body)
161+
if(records) {
162+
for(const [ key, value ] of Object.entries(records)) {
163+
headers[key] = value
164+
}
165+
}
95166
}
96-
97-
options.headers['Accept'] = 'application/json'
98-
options.headers['Content-Type'] = 'application/json'
167+
headers['Accept'] = 'application/json'
168+
headers['Content-Type'] = 'application/json'
99169
try {
100170
const res = await fetch(url, {
101171
method: 'POST',
102172
body: JSON.stringify(options.body),
103-
headers: options.headers,
173+
headers,
104174
})
105175
const json = await res.json()
106176
if(res.status !== 200 && res.status !== 201) {

src/Cqrs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
export interface Base {
22
id: string
33
sid: string
4+
author?: string
45
}
56
export interface BaseResult {
67
id: string
78
sid: string
9+
author?: string
810
error?: {
911
code: string
1012
message: string

src/express/Framework.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class ExpressFramework implements Framework<express.Application> {
131131
code: error.code,
132132
msg: error.message,
133133
})
134-
return await pres.status(500).json({
134+
return await pres.status(422).json({
135135
...rmeta,
136136
error: {
137137
code: error.code,

src/oak/Framework.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,15 @@ export class OakFramework implements Framework<OakApplication> {
145145
code: error.code,
146146
msg: error.message,
147147
})
148-
ctx.response.status = 500,
148+
ctx.response.status = 422,
149149
ctx.response.body = {
150150
...rmeta,
151151
error: {
152152
code: error.code,
153153
message: error.message,
154154
}
155155
}
156+
return
156157
}
157158
else {
158159
log.error(error.stack ? {

0 commit comments

Comments
 (0)