Skip to content

Commit

Permalink
feat!: parse to MIMEType
Browse files Browse the repository at this point in the history
  • Loading branch information
JadsonLucena committed Dec 13, 2023
1 parent b4777aa commit 7587116
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 34 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ constructor(updateInterval?: number = 86400000)

```typescript
// Getters
list(): { [extension: string]: string[] } // List of all extensions with their media types
list(): { [extension: string]: MIMEType[] } // List of all extensions with their media types
updateInterval(): number
versions(): { apache: string, debian: string, nginx: string }
```
Expand All @@ -42,7 +42,7 @@ versions(): { apache: string, debian: string, nginx: string }
* @throws {TypeError} Invalid updateInterval
* @see https://developer.mozilla.org/en-US/docs/Web/API/setInterval#delay
*/
updateInterval(updateInterval?: number = 86400000)
updateInterval(updateInterval?: number = 86400000): void
```

```typescript
Expand All @@ -56,7 +56,7 @@ updateInterval(updateInterval?: number = 86400000)
delete(
extension: string
mediaType: string, // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#structure_of_a_mime_type
) boolean
): boolean

/**
* @method
Expand All @@ -65,7 +65,7 @@ delete(
*/
get(
path: string // https://nodejs.org/api/path.html#pathparsepath
): string[] // Media type list
): MIMEType[] // https://nodejs.org/api/util.html#class-utilmimetype

/**
* @method
Expand All @@ -77,18 +77,18 @@ get(
set(
extension: string
mediaType: string, // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#structure_of_a_mime_type
) boolean
): boolean

/**
* @method
* @fires MediaTypes#update
*/
update(force?: boolean = false): Promise<null | { [extension: string]: string[] }> // List of new inserted media types
update(force?: boolean = false): Promise<null | { [extension: string]: MIMEType[] }> // List of new inserted media types
```

```typescript
// Events
on('update', callback: (list: { [extension: string]: string[] }) => void): void
on('update', callback: (list: { [extension: string]: MIMEType[] }) => void): void
on('error', callback: (error: Error) => void): void
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "This is a comprehensive compilation of media types that is periodically updated through the following projects: Apache, NGINX and Debian",
"main": "./src/MediaTypes.js",
"engines": {
"node": ">=18"
"node": ">=18.13"
},
"scripts": {
"test": "jest ./test",
Expand Down
51 changes: 34 additions & 17 deletions src/MediaTypes.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
'use strict'

const fs = require('node:fs')
const { parse, join } = require('node:path')
const { EventEmitter } = require('node:events')
const { MIMEType } = require('node:util')

function removeDuplicates (array) {
return array.filter((v, i, a) => a.findIndex(t => t.essence === v.essence) === i)
}

/**
* @class
Expand All @@ -19,8 +26,6 @@ class MediaTypes {
#updateInterval
#updateLoop

// https://www.rfc-editor.org/rfc/rfc6838#section-4.2
#formatMediaType = /^(?<type>(x-[a-z0-9]{1,62}|[a-z0-9]{1,64}))\/(?<subtype>[a-z0-9!#$&\-^_.+]{1,64})$/i
#formatExtension = /^[a-z0-9!#$&\-^_+]+$/i

/**
Expand All @@ -40,7 +45,11 @@ class MediaTypes {
try {
const { mediaTypes, versions } = JSON.parse(fs.readFileSync(join(__dirname, 'DB.json')).toString('utf8'))

this.#mediaTypes = mediaTypes
this.#mediaTypes = Object.keys(mediaTypes).reduce((acc, key) => {
acc[key] = mediaTypes[key].map(mediaType => new MIMEType(mediaType))

return acc
}, {})
this.#versions = versions
} catch (err) {
this.#mediaTypes = {}
Expand All @@ -54,6 +63,14 @@ class MediaTypes {
this.updateInterval = updateInterval
}

#isMediaType = mediaType => {
try {
return new MIMEType(mediaType)
} catch (err) {
return false
}
}

#updateList = content => {
const list = {}

Expand All @@ -62,16 +79,16 @@ class MediaTypes {

if (extension in this.#mediaTypes) {
content[extension].forEach(mediaType => {
mediaType = mediaType.trim().toLowerCase()
mediaType = new MIMEType(mediaType)

if (!this.#mediaTypes[extension].includes(mediaType)) {
if (!this.#mediaTypes[extension].some(MT => MT.essence === mediaType.essence)) {
this.#mediaTypes[extension] = this.#mediaTypes[extension].concat(mediaType).sort()

list[extension] = (list[extension] || []).concat(mediaType)
}
})
} else {
list[extension] = this.#mediaTypes[extension] = content[extension]
list[extension] = this.#mediaTypes[extension] = removeDuplicates(content[extension].map(mediaType => new MIMEType(mediaType)))
}
}

Expand All @@ -93,9 +110,9 @@ class MediaTypes {

line = line.match(/^(?<mediaType>[^\s]+)\s+(?<extensions>.*)$/)

const mediaType = line?.groups?.mediaType?.trim()?.toLowerCase()
const mediaType = line?.groups?.mediaType

if (this.#formatMediaType.test(mediaType)) {
if (this.#isMediaType(mediaType)) {
line?.groups?.extensions?.split(/\s+/)?.forEach(extension => {
extension = extension?.trim()?.toLowerCase()

Expand All @@ -116,7 +133,7 @@ class MediaTypes {
*
* @fires MediaTypes#update
*
* @return {Promise<null | Object.<string, string[]>>} List of all extensions with their media types
* @return {Promise<null | Object.<string, MIMEType[]>>} List of all extensions with their media types
*/
update = (force = false) => {
return Promise.allSettled([
Expand Down Expand Up @@ -215,7 +232,7 @@ class MediaTypes {

list = Object.keys(list).reduce((acc, cur) => {
Object.keys(list[cur].content).forEach(key => {
acc[key] = [...new Set((acc[key] || []).concat(list[cur].content[key]))]
acc[key] = removeDuplicates((acc[key] || []).concat(list[cur].content[key]))
})

return acc
Expand All @@ -225,7 +242,7 @@ class MediaTypes {
* Update event
*
* @event MediaTypes#update
* @type {Object.<string, string[]>}
* @type {Object.<string, MIMEType[]>}
*/
this.#eventEmitter.emit('update', list)

Expand All @@ -234,7 +251,7 @@ class MediaTypes {
}

/**
* @return {Object.<string, string[]>}
* @return {Object.<string, MIMEType[]>}
*/
get list () {
return this.#mediaTypes
Expand Down Expand Up @@ -301,7 +318,7 @@ class MediaTypes {
* @throws {TypeError} Invalid path
* @throws {SyntaxError} Invalid extension
*
* @return {string[]}
* @return {MIMEType[]}
*/
get = path => {
if (typeof path !== 'string') {
Expand All @@ -315,7 +332,7 @@ class MediaTypes {
throw new SyntaxError('Invalid extension')
}

return this.#mediaTypes[extension] || []
return (this.#mediaTypes[extension] || [])
}

/**
Expand All @@ -339,7 +356,7 @@ class MediaTypes {

if (typeof mediaType !== 'string') {
throw new TypeError('Invalid mediaType')
} else if (!this.#formatMediaType.test(mediaType)) {
} else if (!this.#isMediaType(mediaType)) {
throw new SyntaxError('Invalid mediaType')
}

Expand Down Expand Up @@ -383,15 +400,15 @@ class MediaTypes {

if (typeof mediaType !== 'string') {
throw new TypeError('Invalid mediaType')
} else if (!this.#formatMediaType.test(mediaType)) {
} else if (!this.#isMediaType(mediaType)) {
throw new SyntaxError('Invalid mediaType')
}

if (!(extension in this.#mediaTypes)) {
return false
}

const i = this.#mediaTypes[extension].indexOf(mediaType.trim().toLowerCase())
const i = this.#mediaTypes[extension].findIndex(MT => MT.essence === new MIMEType(mediaType).essence)

if (i < 0) {
return false
Expand Down
23 changes: 14 additions & 9 deletions test/MediaTypes.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
'use strict'

const fs = require('node:fs')
const { EventEmitter, errorMonitor } = require('node:events')
const { MIMEType } = require('node:util')

jest.useFakeTimers()
jest.spyOn(global, 'setInterval')
Expand Down Expand Up @@ -131,7 +134,7 @@ describe('Attributes', () => {
const mediaType = new MediaTypes()

expect(mediaType.list).toMatchObject({
txt: ['text/plain']
txt: [new MIMEType('text/plain')]
})

expect(mediaType.versions).toMatchObject({
Expand Down Expand Up @@ -197,7 +200,7 @@ describe('Methods', () => {
const mediaType = new MediaTypes(-1);

['path/to/fileName.txt', 'fileName.txt'].forEach(path => {
expect(mediaType.get(path)).toContain('text/plain')
expect(mediaType.get(path)).toContainEqual(new MIMEType('text/plain'))
})
})
})
Expand Down Expand Up @@ -233,17 +236,19 @@ describe('Methods', () => {
const extension = 'test'
const contentType = ['application/x-test', 'application/octet-stream']

expect(mediaType.set(extension, contentType[0])).toBeTruthy()
expect(mediaType.get(`fileName.${extension}`)).toContain(contentType[0])
expect(mediaType.set(extension, `${contentType[0]};key=value`)).toBeTruthy()
expect(mediaType.set(extension, contentType[0])).toBeFalsy()
expect(mediaType.get(`fileName.${extension}`)).toContainEqual(new MIMEType(`${contentType[0]};key=value`))

expect(mediaType.set(extension, contentType[1])).toBeTruthy()
expect(mediaType.get(`fileName.${extension}`)).toContain(contentType[1])
expect(mediaType.get(`fileName.${extension}`)).toContainEqual(new MIMEType(contentType[1]))
})

test('Given that one wants to set an already existing media type into the module list', () => {
const mediaType = new MediaTypes(-1)

expect(mediaType.set('txt', 'text/plain')).toBeFalsy()
expect(mediaType.set('txt', 'text/plain;key=value')).toBeFalsy()
})
})

Expand Down Expand Up @@ -280,7 +285,7 @@ describe('Methods', () => {

expect(mediaType.delete(extension, contentType)).toBeTruthy()
expect(extension in mediaType.list).toBeFalsy()
expect(mediaType.get(`fileName.${extension}`)).not.toContain(contentType)
expect(mediaType.get(`fileName.${extension}`)).not.toContainEqual(new MIMEType(contentType))
})

test('Given that one wants to delete a media type that does not exist in the module list', () => {
Expand All @@ -297,7 +302,7 @@ describe('Methods', () => {

expect(mediaType.set(extension, 'application/octet-stream')).toBeTruthy()
expect(mediaType.delete(extension, 'text/plain')).toBeTruthy()
expect(mediaType.get(`fileName.${extension}`)).toContain('application/octet-stream')
expect(mediaType.get(`fileName.${extension}`)).toContainEqual(new MIMEType('application/octet-stream'))
})
})

Expand All @@ -315,7 +320,7 @@ describe('Methods', () => {
await expect(mediaType.update().then(res => {
return Object.keys(res)
})).resolves.toContain(extension)
expect(mediaType.get(`fileName.${extension}`)).toContain(contentType)
expect(mediaType.get(`fileName.${extension}`)).toContainEqual(new MIMEType(contentType))
})

test('Given that one wants to try to force update the list of media types at some point', async () => {
Expand All @@ -336,7 +341,7 @@ describe('Methods', () => {
return Object.keys(res)
})).resolves.toContain(extension)

expect(mediaType.get(`fileName.${extension}`)).toContain(contentType)
expect(mediaType.get(`fileName.${extension}`)).toContainEqual(new MIMEType(contentType))
})
})
})
Expand Down

0 comments on commit 7587116

Please sign in to comment.