Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify parsing into a util and add support for Date inside object #105

Merged
merged 3 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 2 additions & 24 deletions src/fetch/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import type { Elysia } from 'elysia'

import { isFormalDate, isISO8601, isShortenDate } from '../treaty2'

import { EdenFetchError } from '../errors'
import type { EdenFetch } from './types'
import { isNumericString } from '../treaty/utils'
import { parseStringifiedValue } from '../utils/parsingUtils'

export type { EdenFetch } from './types'

Expand Down Expand Up @@ -75,27 +73,7 @@ export const edenFetch =
break

default:
data = await response.text().then((data) => {
if (isNumericString(data)) return +data
if (data === 'true') return true
if (data === 'false') return false

if (!data) return data

// Remove quote from stringified date
const temp = data.replace(/"/g, '')

if (
isISO8601.test(temp) ||
isFormalDate.test(temp) ||
isShortenDate.test(temp)
) {
const date = new Date(temp)
if (!Number.isNaN(date.getTime())) return date
}

return data
})
data = await response.text().then(parseStringifiedValue)
}

if (response.status > 300)
Expand Down
3 changes: 2 additions & 1 deletion src/treaty/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import type { Elysia, InputSchema } from 'elysia'

import { EdenFetchError } from '../errors'

import { composePath, isNumericString } from './utils'
import { composePath } from './utils'
import type { EdenTreaty } from './types'
import { isNumericString } from '../utils/parsingUtils'

export type { EdenTreaty } from './types'

Expand Down
3 changes: 0 additions & 3 deletions src/treaty/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,3 @@ export const composePath = (

return `${domain}${path}?${q.slice(0, -1)}`
}

export const isNumericString = (message: string) =>
message.trim().length !== 0 && !Number.isNaN(Number(message))
146 changes: 44 additions & 102 deletions src/treaty2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import type { Elysia } from 'elysia'
import type { Treaty } from './types'

import { composePath, isNumericString } from '../treaty/utils'
import { EdenFetchError } from '../errors'
import { EdenWS } from './ws'
import { parseStringifiedValue } from '../utils/parsingUtils'

const method = [
'get',
Expand All @@ -22,13 +22,6 @@ const locals = ['localhost', '127.0.0.1', '0.0.0.0']

const isServer = typeof FileList === 'undefined'

export const isISO8601 =
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/
export const isFormalDate =
/(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{2}\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT(?:\+|-)\d{4}\s\([^)]+\)/
export const isShortenDate =
/^(?:(?:(?:(?:0?[1-9]|[12][0-9]|3[01])[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:19|20)\d{2})|(?:(?:19|20)\d{2}[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:0?[1-9]|[12][0-9]|3[01]))))(?:\s(?:1[012]|0?[1-9]):[0-5][0-9](?::[0-5][0-9])?(?:\s[AP]M)?)?$/

const isFile = (v: any) => {
if (isServer) return v instanceof Blob

Expand Down Expand Up @@ -130,46 +123,7 @@ export async function* streamResponse(response: Response) {

const data = decoder.decode(value)

if (isNumericString(data)) {
yield +data
continue
}

if (data === 'true') {
yield true
continue
}

if (data === 'false') {
yield false
continue
}

const start = data.charCodeAt(0)
const end = data.charCodeAt(data.length - 1)

if ((start === 123 && end === 125) || (start === 91 && end === 93))
try {
yield JSON.parse(data)

continue
} catch {}

// Remove quote from stringified date
const temp = data.replace(/"/g, '')

if (
isISO8601.test(temp) ||
isFormalDate.test(temp) ||
isShortenDate.test(temp)
) {
const date = new Date(temp)
if (!Number.isNaN(date.getTime())) yield date

continue
}

yield data
yield parseStringifiedValue(data)
}
} finally {
reader.releaseLock()
Expand Down Expand Up @@ -431,62 +385,50 @@ const createProxy = (
}
}

if (data === null) {
switch (
response.headers.get('Content-Type')?.split(';')[0]
) {
case 'text/event-stream':
data = streamResponse(response)
break

case 'application/json':
data = await response.json()
break

case 'application/octet-stream':
data = await response.arrayBuffer()
break

case 'multipart/form-data':
const temp = await response.formData()

data = {}
temp.forEach((value, key) => {
// @ts-ignore
data[key] = value
})

break

default:
data = await response.text().then((data) => {
if (isNumericString(data)) return +data
if (data === 'true') return true
if (data === 'false') return false

if (!data) return data

// Remove quote from stringified date
const temp = data.replace(/"/g, '')

if (
isISO8601.test(temp) ||
isFormalDate.test(temp) ||
isShortenDate.test(temp)
) {
const date = new Date(temp)
if (!Number.isNaN(date.getTime()))
return date
}

return data
})
if (data !== null) {
return {
data,
error,
response,
status: response.status,
headers: response.headers
}
}

if (response.status >= 300 || response.status < 200) {
error = new EdenFetchError(response.status, data)
data = null
}
switch (
response.headers.get('Content-Type')?.split(';')[0]
) {
case 'text/event-stream':
data = streamResponse(response)
break

case 'application/json':
data = await response.json()
break
case 'application/octet-stream':
data = await response.arrayBuffer()
break

case 'multipart/form-data':
const temp = await response.formData()

data = {}
temp.forEach((value, key) => {
// @ts-ignore
data[key] = value
})

break

default:
data = await response
.text()
.then(parseStringifiedValue) // Since this change, if the string contains a stringified JSOn, it will be parsed as such. Is it OK?
}

if (response.status >= 300 || response.status < 200) {
error = new EdenFetchError(response.status, data)
data = null
}

return {
Expand Down
17 changes: 11 additions & 6 deletions src/treaty2/ws.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { InputSchema } from 'elysia'
import type { Treaty } from './types'
import { isNumericString } from '../treaty/utils'
import { isNumericString } from '../utils/parsingUtils'

const ISO8601DateString = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/

Expand Down Expand Up @@ -67,23 +67,28 @@ export class EdenWS<in out Schema extends InputSchema<any> = {}> {
if (start === 91 || start === 123)
try {
data = JSON.parse(data, (key, value) => {
if(typeof value === 'string' && ISO8601DateString.test(value)) {
if (
typeof value === 'string' &&
ISO8601DateString.test(value)
) {
const d = new Date(value)
if(!Number.isNaN(d.getTime()))
return d
if (!Number.isNaN(d.getTime())) return d
}
return value
})
} catch {
// Not Empty
}

else if (isNumericString(data)) data = +data
else if (data === 'true') data = true
else if (data === 'false') data = false
else if (data === 'null') data = null
// Remove " before parsing
else if (start === 34 && end === 34 && ISO8601DateString.test(data))
else if (
start === 34 &&
end === 34 &&
ISO8601DateString.test(data)
)
data = new Date(data.substring(1, data.length - 1))

listener({
Expand Down
82 changes: 82 additions & 0 deletions src/utils/parsingUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const isISO8601 =
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/
const isFormalDate =
/(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{2}\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT(?:\+|-)\d{4}\s\([^)]+\)/
const isShortenDate =
/^(?:(?:(?:(?:0?[1-9]|[12][0-9]|3[01])[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:19|20)\d{2})|(?:(?:19|20)\d{2}[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:0?[1-9]|[12][0-9]|3[01]))))(?:\s(?:1[012]|0?[1-9]):[0-5][0-9](?::[0-5][0-9])?(?:\s[AP]M)?)?$/

export const isNumericString = (message: string) =>
message.trim().length !== 0 && !Number.isNaN(Number(message))

export const parseStringifiedDate = (value: any) => {
if (typeof value !== 'string') {
return null
}

// Remove quote from stringified date
const temp = value.replace(/"/g, '')

if (
isISO8601.test(temp) ||
isFormalDate.test(temp) ||
isShortenDate.test(temp)
) {
const date = new Date(temp)

if (!Number.isNaN(date.getTime())) {
return date
}
}

return null
}

export const isStringifiedObject = (value: string) => {
const start = value.charCodeAt(0)
const end = value.charCodeAt(value.length - 1)

return (start === 123 && end === 125) || (start === 91 && end === 93)
}

export const parseStringifiedObject = (data: string) =>
JSON.parse(data, (_, value) => {
const date = parseStringifiedDate(value)

if (date) {
return date
}

return value
})

export const parseStringifiedValue = (value: any) => {
if (!value) {
return value
}

if (isNumericString(value)) {
return +value
}

if (value === 'true') {
return true
}

if (value === 'false') {
return false
}

const date = parseStringifiedDate(value)

if (date) {
return date
}

if (isStringifiedObject(value)) {
try {
return parseStringifiedObject(value)
} catch {}
}

return value
}
7 changes: 7 additions & 0 deletions test/treaty2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ const app = new Elysia()
date: t.Date()
})
})
.get('/dateObject', () => ({ date: new Date() }))
.get(
'/redirect',
({ set }) => (set.redirect = 'http://localhost:8083/true')
Expand Down Expand Up @@ -190,6 +191,12 @@ describe('Treaty2', () => {
expect(data).toEqual(false)
})

it.todo('parse object with date', async () => {
const { data } = await client.dateObject.get()

expect(data?.date).toBeInstanceOf(Date)
})

it('post array', async () => {
const { data } = await client.array.post(['a', 'b'])

Expand Down
Loading