http-svc
Version:
A HTTP request service for browser and node.js
212 lines (193 loc) • 6.04 kB
text/typescript
import { BuiltInMiddleware, BuiltInMiddlewareName, IHttpSvcContext } from 'types/exports'
import qs, { IStringifyOptions } from 'qs'
export const getBuiltInMiddlewareName = (name: BuiltInMiddleware): BuiltInMiddlewareName => `BUILT_IN_${name}`
export const isMiddleware = (middleware: any) => {
if ((middleware as any).name && (middleware as any).handler) return true
return false
}
interface IHttpSvcError extends Error {
config?: IHttpSvcContext['config']
response?: IHttpSvcContext['response']
status?: number
[key: string]: any
}
export const createDefaultError = (ctx: IHttpSvcContext, message?: string): IHttpSvcError => {
const status = ctx.response?.status
const def = 'Request Error'
let msg = ''
if (message) {
msg = message
} else if (typeof status === 'number') {
msg = `${def}: ${status}`
} else {
msg = def
}
return new Error(msg) as IHttpSvcError
}
/**
* 创建error
* e 可以是 Error | string | 或者对象上可访问message属性的对象
*/
export const createError = (ctx: IHttpSvcContext, e?: any): IHttpSvcError => {
let error: IHttpSvcError
if (typeof e === 'undefined') {
error = createDefaultError(ctx)
} else {
if (e instanceof Error) {
error = e as IHttpSvcError
} else {
error = createDefaultError(ctx, typeof e === 'string' ? e : e?.message)
}
}
const status = ctx.response?.status
if (ctx.request) {
error.config = {
url: ctx.request.url!,
method: ctx.request.method!,
params: ctx.request.params!,
data: ctx.request.data!,
headers: ctx.request.headers!
}
} else {
error.config = ctx.config
}
if (ctx.response) {
error.response = ctx.response
if (!ctx.response.ok && status) {
error.status = status
}
}
return error
}
export const isNode: boolean = typeof window === 'undefined'
export const isArray = (val: any) => toString.call(val) === '[object Array]'
export const isObject = (val: any) => val !== null && typeof val === 'object'
export const isRecordObj = (obj: any) => Object.prototype.toString.call(obj) === '[object Object]'
export const isFormData = (val: any) => typeof FormData !== 'undefined' && val instanceof FormData
export const paramsSerializer = (params: Record<string, unknown>, options?: IStringifyOptions) => {
return qs.stringify(
params,
options || {
encode: true,
arrayFormat: 'brackets'
}
)
}
export const buildURL = (
url: string,
params: { [x: string]: unknown },
ps?: ((params: Record<string, unknown>) => string) | IStringifyOptions
): string => {
let serializedParams = ''
if (Object.keys(params).length === 0) {
return url
}
if (typeof ps === 'function') {
serializedParams = ps(params)
} else {
const qsOptions: IStringifyOptions = {
encode: true,
arrayFormat: 'brackets'
}
if (ps && typeof ps === 'object') {
Object.assign(qsOptions, ps)
}
serializedParams = paramsSerializer(params, qsOptions)
}
if (serializedParams) {
const hashMarkIndex = url.indexOf('#')
if (hashMarkIndex !== -1) {
url = url.slice(0, hashMarkIndex) as string
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
}
return url
}
export const parseURL = (url: string): { url: string; params?: Record<string, string> } => {
const [u, s] = url.split('?')
if (s) {
const searchParams = s.split('&')
if (searchParams.length) {
const params: Record<string, string> = {}
for (const searchParam of searchParams) {
const [key, value] = searchParam.split('=')
if (key && typeof value !== 'undefined') {
params[key] = value
}
}
return {
url: u,
params
}
}
}
return {
url
}
}
export const formatString = (input: string) => {
let afterFormat = ''
for (let i = 0; i < input.length; i++) {
afterFormat += String.fromCharCode(input.charCodeAt(i) - 1)
}
return afterFormat
}
// 标准的content-type key写法
const CONTENT_TYPE_KEY = 'Content-Type'
export enum ContentType {
JSON = 'application/json',
Form = 'application/x-www-form-urlencoded',
FormData = 'multipart/form-data'
}
export const isEqualContentType = (target: string, current?: string) => {
if (!current) return false
return current.indexOf(target) > -1
}
// 从headers里取contenttype
export const getContentType = (headers?: Record<string, string>) => headers?.[CONTENT_TYPE_KEY] || headers?.['content-type'] // 非标准写法
export const setContentType = (headers: Record<string, string>, contentType: string) => {
if (!contentType) return
headers[CONTENT_TYPE_KEY] = contentType
}
export const getCookie = (key: string, options?: { decode?: boolean; template?: string }) => {
if (!options) options = {}
const isNoDecode = options.decode === false
const template = options.template || document.cookie
const decodedCookie = isNoDecode ? template : decodeURIComponent(template)
const cookies = decodedCookie.split(';')
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i]
while (cookie.charAt(0) === ' ') {
cookie = cookie.substring(1)
}
const keyVal = cookie.split('=')
const name = keyVal[0]
const value = keyVal[1]
if (key === name) return value
}
return ''
}
export const setObjectValue = (obj: Record<string, any>, keyOrKeys: string | string[], value: any, spread?: boolean): void => {
if (Array.isArray(keyOrKeys)) {
const keys = [...keyOrKeys]
while (keys.length) {
if (keys.length === 1) {
setObjectValue(obj, keys[0], value)
break
}
const key = keys.shift() as string
if (!isRecordObj(obj)) {
if (typeof obj[key] === 'undefined' && spread) {
obj[key] = {}
} else {
console.warn(`The value of "${key}" is not a record object!`)
break
}
}
obj = obj[key]
}
} else {
const key = keyOrKeys
obj[key] = value
}
}