diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 62562b7..0000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -coverage -node_modules diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index cf3015f..0000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,9 +0,0 @@ -root: true -extends: - - standard - - plugin:markdown/recommended -plugins: - - markdown -overrides: - - files: '**/*.md' - processor: 'markdown/markdown' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49662aa..8fff328 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,128 +1,26 @@ -name: ci - +name: CI on: - push: - branches: - - master - - '2.x' - paths-ignore: - - '*.md' - pull_request: - paths-ignore: - - '*.md' - + - push + - pull_request permissions: contents: read - jobs: test: - permissions: - checks: write # for coverallsapp/github-action to create new checks - contents: read # for actions/checkout to fetch code + name: Node.js ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: matrix: - name: - - Node.js 18.x - - Node.js 19.x - - Node.js 20.x - - Node.js 21.x - - Node.js 22.x - - include: - - name: Node.js 18.x - node-version: "18" - - - name: Node.js 19.x - node-version: "19" - - - name: Node.js 20.x - node-version: "20" - - - name: Node.js 21.x - node-version: "21" - - - name: Node.js 22.x - node-version: "22" - + node-version: + - 18 + - '*' steps: - - uses: actions/checkout@v5.0.0 - - - name: Install Node.js ${{ matrix.node-version }} - shell: bash -eo pipefail -l {0} - run: | - nvm install --default ${{ matrix.node-version }} - dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" - - - name: Configure npm - run: | - if [[ "$(npm config get package-lock)" == "true" ]]; then - npm config set package-lock false - else - npm config set shrinkwrap false - fi - - - name: Remove npm module(s) ${{ matrix.npm-rm }} - run: npm rm --silent --save-dev ${{ matrix.npm-rm }} - if: matrix.npm-rm != '' - - - name: Install npm module(s) ${{ matrix.npm-i }} - run: npm install --save-dev ${{ matrix.npm-i }} - if: matrix.npm-i != '' - - - name: Setup Node.js version-specific dependencies - shell: bash - run: | - # eslint for linting - # - remove on Node.js < 10 - if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then - node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ - grep -E '^eslint(-|$)' | \ - sort -r | \ - xargs -n1 npm rm --silent --save-dev - fi - - - name: Install Node.js dependencies - run: npm install - - - name: List environment - id: list_env - shell: bash - run: | - echo "node@$(node -v)" - echo "npm@$(npm -v)" - npm -s ls ||: - (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "::set-output name=" $2 "::" $3 }' - - - name: Run tests - shell: bash - run: | - if npm -ps ls nyc | grep -q nyc; then - npm run test-ci - else - npm test - fi - - - name: Lint code - if: steps.list_env.outputs.eslint != '' - run: npm run lint - - - name: Collect code coverage - uses: coverallsapp/github-action@master - if: steps.list_env.outputs.nyc != '' - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - flag-name: run-${{ matrix.test_number }} - parallel: true - - coverage: - permissions: - checks: write # for coverallsapp/github-action to create new checks - needs: test - runs-on: ubuntu-latest - steps: - - name: Upload code coverage - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - parallel-finished: true + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm test + - uses: codecov/codecov-action@v5 + with: + name: Node.js ${{ matrix.node-version }} + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index f15b98e..d364eaa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -.nyc_output/ coverage/ +dist/ node_modules/ npm-debug.log package-lock.json +*.tsbuildinfo \ No newline at end of file diff --git a/HISTORY.md b/HISTORY.md deleted file mode 100644 index 6812655..0000000 --- a/HISTORY.md +++ /dev/null @@ -1,292 +0,0 @@ -2.0.1 / 2025-03-27 -========== - -2.0.0 / 2024-08-31 -========== - - * Drop node <18 - * Use `content-type@^1.0.5` and `media-typer@^1.0.0` for type validation - - No behavior changes, upgrades `media-typer` - * deps: mime-types@^3.0.0 - - Add `application/toml` with extension `.toml` - - Add `application/ubjson` with extension `.ubj` - - Add `application/x-keepass2` with extension `.kdbx` - - Add deprecated iWorks mime types and extensions - - Add extension `.amr` to `audio/amr` - - Add extension `.cjs` to `application/node` - - Add extension `.dbf` to `application/vnd.dbf` - - Add extension `.m4s` to `video/iso.segment` - - Add extension `.mvt` to `application/vnd.mapbox-vector-tile` - - Add extension `.mxmf` to `audio/mobile-xmf` - - Add extension `.opus` to `audio/ogg` - - Add extension `.rar` to `application/vnd.rar` - - Add extension `.td` to `application/urc-targetdesc+xml` - - Add extension `.trig` to `application/trig` - - Add extensions from IANA for `application/*+xml` types - - Add `image/avif` with extension `.avif` - - Add `image/ktx2` with extension `.ktx2` - - Add `image/vnd.ms-dds` with extension `.dds` - - Add new upstream MIME types - - Fix extension of `application/vnd.apple.keynote` to be `.key` - - Remove ambigious extensions from IANA for `application/*+xml` types - - Update primary extension to `.es` for `application/ecmascript` - -1.6.18 / 2019-04-26 -=================== - - * Fix regression passing request object to `typeis.is` - -1.6.17 / 2019-04-25 -=================== - - * deps: mime-types@~2.1.24 - - Add Apple file extensions from IANA - - Add extension `.csl` to `application/vnd.citationstyles.style+xml` - - Add extension `.es` to `application/ecmascript` - - Add extension `.nq` to `application/n-quads` - - Add extension `.nt` to `application/n-triples` - - Add extension `.owl` to `application/rdf+xml` - - Add extensions `.siv` and `.sieve` to `application/sieve` - - Add extensions from IANA for `image/*` types - - Add extensions from IANA for `model/*` types - - Add extensions to HEIC image types - - Add new mime types - - Add `text/mdx` with extension `.mdx` - * perf: prevent internal `throw` on invalid type - -1.6.16 / 2018-02-16 -=================== - - * deps: mime-types@~2.1.18 - - Add `application/raml+yaml` with extension `.raml` - - Add `application/wasm` with extension `.wasm` - - Add `text/shex` with extension `.shex` - - Add extensions for JPEG-2000 images - - Add extensions from IANA for `message/*` types - - Add extension `.mjs` to `application/javascript` - - Add extension `.wadl` to `application/vnd.sun.wadl+xml` - - Add extension `.gz` to `application/gzip` - - Add glTF types and extensions - - Add new mime types - - Update extensions `.md` and `.markdown` to be `text/markdown` - - Update font MIME types - - Update `text/hjson` to registered `application/hjson` - -1.6.15 / 2017-03-31 -=================== - - * deps: mime-types@~2.1.15 - - Add new mime types - -1.6.14 / 2016-11-18 -=================== - - * deps: mime-types@~2.1.13 - - Add new mime types - -1.6.13 / 2016-05-18 -=================== - - * deps: mime-types@~2.1.11 - - Add new mime types - -1.6.12 / 2016-02-28 -=================== - - * deps: mime-types@~2.1.10 - - Add new mime types - - Fix extension of `application/dash+xml` - - Update primary extension for `audio/mp4` - -1.6.11 / 2016-01-29 -=================== - - * deps: mime-types@~2.1.9 - - Add new mime types - -1.6.10 / 2015-12-01 -=================== - - * deps: mime-types@~2.1.8 - - Add new mime types - -1.6.9 / 2015-09-27 -================== - - * deps: mime-types@~2.1.7 - - Add new mime types - -1.6.8 / 2015-09-04 -================== - - * deps: mime-types@~2.1.6 - - Add new mime types - -1.6.7 / 2015-08-20 -================== - - * Fix type error when given invalid type to match against - * deps: mime-types@~2.1.5 - - Add new mime types - -1.6.6 / 2015-07-31 -================== - - * deps: mime-types@~2.1.4 - - Add new mime types - -1.6.5 / 2015-07-16 -================== - - * deps: mime-types@~2.1.3 - - Add new mime types - -1.6.4 / 2015-07-01 -================== - - * deps: mime-types@~2.1.2 - - Add new mime types - * perf: enable strict mode - * perf: remove argument reassignment - -1.6.3 / 2015-06-08 -================== - - * deps: mime-types@~2.1.1 - - Add new mime types - * perf: reduce try block size - * perf: remove bitwise operations - -1.6.2 / 2015-05-10 -================== - - * deps: mime-types@~2.0.11 - - Add new mime types - -1.6.1 / 2015-03-13 -================== - - * deps: mime-types@~2.0.10 - - Add new mime types - -1.6.0 / 2015-02-12 -================== - - * fix false-positives in `hasBody` `Transfer-Encoding` check - * support wildcard for both type and subtype (`*/*`) - -1.5.7 / 2015-02-09 -================== - - * fix argument reassignment - * deps: mime-types@~2.0.9 - - Add new mime types - -1.5.6 / 2015-01-29 -================== - - * deps: mime-types@~2.0.8 - - Add new mime types - -1.5.5 / 2014-12-30 -================== - - * deps: mime-types@~2.0.7 - - Add new mime types - - Fix missing extensions - - Fix various invalid MIME type entries - - Remove example template MIME types - - deps: mime-db@~1.5.0 - -1.5.4 / 2014-12-10 -================== - - * deps: mime-types@~2.0.4 - - Add new mime types - - deps: mime-db@~1.3.0 - -1.5.3 / 2014-11-09 -================== - - * deps: mime-types@~2.0.3 - - Add new mime types - - deps: mime-db@~1.2.0 - -1.5.2 / 2014-09-28 -================== - - * deps: mime-types@~2.0.2 - - Add new mime types - - deps: mime-db@~1.1.0 - -1.5.1 / 2014-09-07 -================== - - * Support Node.js 0.6 - * deps: media-typer@0.3.0 - * deps: mime-types@~2.0.1 - - Support Node.js 0.6 - -1.5.0 / 2014-09-05 -================== - - * fix `hasbody` to be true for `content-length: 0` - -1.4.0 / 2014-09-02 -================== - - * update mime-types - -1.3.2 / 2014-06-24 -================== - - * use `~` range on mime-types - -1.3.1 / 2014-06-19 -================== - - * fix global variable leak - -1.3.0 / 2014-06-19 -================== - - * improve type parsing - - - invalid media type never matches - - media type not case-sensitive - - extra LWS does not affect results - -1.2.2 / 2014-06-19 -================== - - * fix behavior on unknown type argument - -1.2.1 / 2014-06-03 -================== - - * switch dependency from `mime` to `mime-types@1.0.0` - -1.2.0 / 2014-05-11 -================== - - * support suffix matching: - - - `+json` matches `application/vnd+json` - - `*/vnd+json` matches `application/vnd+json` - - `application/*+json` matches `application/vnd+json` - -1.1.0 / 2014-04-12 -================== - - * add non-array values support - * expose internal utilities: - - - `.is()` - - `.hasBody()` - - `.normalize()` - - `.match()` - -1.0.1 / 2014-03-30 -================== - - * add `multipart` as a shorthand diff --git a/README.md b/README.md index d23946e..de1845d 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,6 @@ Infer the content-type of a request. ## Install -This is a [Node.js](https://nodejs.org/en/) module available through the -[npm registry](https://www.npmjs.com/). Installation is done using the -[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): - ```sh $ npm install type-is ``` @@ -21,16 +17,16 @@ $ npm install type-is ## API ```js -var http = require('http') -var typeis = require('type-is') +import { createServer } from "http"; +import * as typeis from "type-is"; http.createServer(function (req, res) { - var istext = typeis(req, ['text/*']) - res.end('you ' + (istext ? 'sent' : 'did not send') + ' me text') -}) + var istext = typeis.request(req, ["text/*"]); + res.end("you " + (istext ? "sent" : "did not send") + " me text"); +}); ``` -### typeis(request, types) +### request(req, types) Checks if the `request` is one of the `types`. If the request has no body, even if there is a `Content-Type` header, then `null` is returned. If the @@ -55,12 +51,12 @@ Some examples to illustrate the inputs and returned value: ```js // req.headers.content-type = 'application/json' -typeis(req, ['json']) // => 'json' -typeis(req, ['html', 'json']) // => 'json' -typeis(req, ['application/*']) // => 'application/json' -typeis(req, ['application/json']) // => 'application/json' +request(req, ["json"]); // => 'json' +request(req, ["html", "json"]); // => 'json' +request(req, ["application/*"]); // => 'application/json' +request(req, ["application/json"]); // => 'application/json' -typeis(req, ['html']) // => false +request(req, ["html"]); // => false ``` ### typeis.hasBody(request) @@ -76,9 +72,9 @@ indicates that there is data to read from the Node.js request stream. if (typeis.hasBody(req)) { // read the body, since there is one - req.on('data', function (chunk) { + req.on("data", function (chunk) { // ... - }) + }); } ``` @@ -105,14 +101,14 @@ Each type in the `types` array can be one of the following: Some examples to illustrate the inputs and returned value: ```js -var mediaType = 'application/json' +var mediaType = "application/json"; -typeis.is(mediaType, ['json']) // => 'json' -typeis.is(mediaType, ['html', 'json']) // => 'json' -typeis.is(mediaType, ['application/*']) // => 'application/json' -typeis.is(mediaType, ['application/json']) // => 'application/json' +typeis.is(mediaType, ["json"]); // => 'json' +typeis.is(mediaType, ["html", "json"]); // => 'json' +typeis.is(mediaType, ["application/*"]); // => 'application/json' +typeis.is(mediaType, ["application/json"]); // => 'application/json' -typeis.is(mediaType, ['html']) // => false +typeis.is(mediaType, ["html"]); // => false ``` ### typeis.match(expected, actual) @@ -124,11 +120,11 @@ suffix can still be included even with a wildcard subtype. If an input is malformed, `false` will be returned. ```js -typeis.match('text/html', 'text/html') // => true -typeis.match('*/html', 'text/html') // => true -typeis.match('text/*', 'text/html') // => true -typeis.match('*/*', 'text/html') // => true -typeis.match('*/*+json', 'application/x-custom+json') // => true +typeis.match("text/html", "text/html"); // => true +typeis.match("*/html", "text/html"); // => true +typeis.match("text/*", "text/html"); // => true +typeis.match("*/*", "text/html"); // => true +typeis.match("*/*+json", "application/x-custom+json"); // => true ``` ### typeis.normalize(type) @@ -152,33 +148,33 @@ This includes two special mappings: ### Example body parser ```js -var express = require('express') -var typeis = require('type-is') +var express = require("express"); +var typeis = require("type-is"); -var app = express() +var app = express(); -app.use(function bodyParser (req, res, next) { +app.use(function bodyParser(req, res, next) { if (!typeis.hasBody(req)) { - return next() + return next(); } - switch (typeis(req, ['urlencoded', 'json', 'multipart'])) { - case 'urlencoded': + switch (typeis.request(req, ["urlencoded", "json", "multipart"])) { + case "urlencoded": // parse urlencoded body - throw new Error('implement urlencoded body parsing') - case 'json': + throw new Error("implement urlencoded body parsing"); + case "json": // parse json body - throw new Error('implement json body parsing') - case 'multipart': + throw new Error("implement json body parsing"); + case "multipart": // parse multipart body - throw new Error('implement multipart body parsing') + throw new Error("implement multipart body parsing"); default: // 415 error code - res.statusCode = 415 - res.end() - break + res.statusCode = 415; + res.end(); + break; } -}) +}); ``` ## License diff --git a/index.js b/index.js deleted file mode 100644 index e773845..0000000 --- a/index.js +++ /dev/null @@ -1,250 +0,0 @@ -/*! - * type-is - * Copyright(c) 2014 Jonathan Ong - * Copyright(c) 2014-2015 Douglas Christopher Wilson - * MIT Licensed - */ - -'use strict' - -/** - * Module dependencies. - * @private - */ - -var contentType = require('content-type') -var mime = require('mime-types') -var typer = require('media-typer') - -/** - * Module exports. - * @public - */ - -module.exports = typeofrequest -module.exports.is = typeis -module.exports.hasBody = hasbody -module.exports.normalize = normalize -module.exports.match = mimeMatch - -/** - * Compare a `value` content-type with `types`. - * Each `type` can be an extension like `html`, - * a special shortcut like `multipart` or `urlencoded`, - * or a mime type. - * - * If no types match, `false` is returned. - * Otherwise, the first `type` that matches is returned. - * - * @param {String} value - * @param {Array} types - * @public - */ - -function typeis (value, types_) { - var i - var types = types_ - - // remove parameters and normalize - var val = tryNormalizeType(value) - - // no type or invalid - if (!val) { - return false - } - - // support flattened arguments - if (types && !Array.isArray(types)) { - types = new Array(arguments.length - 1) - for (i = 0; i < types.length; i++) { - types[i] = arguments[i + 1] - } - } - - // no types, return the content type - if (!types || !types.length) { - return val - } - - var type - for (i = 0; i < types.length; i++) { - if (mimeMatch(normalize(type = types[i]), val)) { - return type[0] === '+' || type.indexOf('*') !== -1 - ? val - : type - } - } - - // no matches - return false -} - -/** - * Check if a request has a request body. - * A request with a body __must__ either have `transfer-encoding` - * or `content-length` headers set. - * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3 - * - * @param {Object} request - * @return {Boolean} - * @public - */ - -function hasbody (req) { - return req.headers['transfer-encoding'] !== undefined || - !isNaN(req.headers['content-length']) -} - -/** - * Check if the incoming request contains the "Content-Type" - * header field, and it contains any of the give mime `type`s. - * If there is no request body, `null` is returned. - * If there is no content type, `false` is returned. - * Otherwise, it returns the first `type` that matches. - * - * Examples: - * - * // With Content-Type: text/html; charset=utf-8 - * this.is('html'); // => 'html' - * this.is('text/html'); // => 'text/html' - * this.is('text/*', 'application/json'); // => 'text/html' - * - * // When Content-Type is application/json - * this.is('json', 'urlencoded'); // => 'json' - * this.is('application/json'); // => 'application/json' - * this.is('html', 'application/*'); // => 'application/json' - * - * this.is('html'); // => false - * - * @param {Object} req - * @param {(String|Array)} types... - * @return {(String|false|null)} - * @public - */ - -function typeofrequest (req, types_) { - // no body - if (!hasbody(req)) return null - // support flattened arguments - var types = arguments.length > 2 - ? Array.prototype.slice.call(arguments, 1) - : types_ - // request content type - var value = req.headers['content-type'] - - return typeis(value, types) -} - -/** - * Normalize a mime type. - * If it's a shorthand, expand it to a valid mime type. - * - * In general, you probably want: - * - * var type = is(req, ['urlencoded', 'json', 'multipart']); - * - * Then use the appropriate body parsers. - * These three are the most common request body types - * and are thus ensured to work. - * - * @param {String} type - * @return {String|false|null} - * @public - */ - -function normalize (type) { - if (typeof type !== 'string') { - // invalid type - return false - } - - switch (type) { - case 'urlencoded': - return 'application/x-www-form-urlencoded' - case 'multipart': - return 'multipart/*' - } - - if (type[0] === '+') { - // "+json" -> "*/*+json" expando - return '*/*' + type - } - - return type.indexOf('/') === -1 - ? mime.lookup(type) - : type -} - -/** - * Check if `expected` mime type - * matches `actual` mime type with - * wildcard and +suffix support. - * - * @param {String} expected - * @param {String} actual - * @return {Boolean} - * @public - */ - -function mimeMatch (expected, actual) { - // invalid type - if (expected === false) { - return false - } - - // split types - var actualParts = actual.split('/') - var expectedParts = expected.split('/') - - // invalid format - if (actualParts.length !== 2 || expectedParts.length !== 2) { - return false - } - - // validate type - if (expectedParts[0] !== '*' && expectedParts[0] !== actualParts[0]) { - return false - } - - // validate suffix wildcard - if (expectedParts[1].slice(0, 2) === '*+') { - return expectedParts[1].length <= actualParts[1].length + 1 && - expectedParts[1].slice(1) === actualParts[1].slice(1 - expectedParts[1].length) - } - - // validate subtype - if (expectedParts[1] !== '*' && expectedParts[1] !== actualParts[1]) { - return false - } - - return true -} - -/** - * Normalize a type and remove parameters. - * - * @param {string} value - * @return {(string|null)} - * @private - */ -function normalizeType (value) { - // Parse the type - var type = contentType.parse(value).type - - return typer.test(type) ? type : null -} - -/** - * Try to normalize a type and remove parameters. - * - * @param {string} value - * @return {(string|null)} - * @private - */ -function tryNormalizeType (value) { - try { - return value ? normalizeType(value) : null - } catch (err) { - return null - } -} diff --git a/package.json b/package.json index 494e864..d142b20 100644 --- a/package.json +++ b/package.json @@ -1,51 +1,59 @@ { "name": "type-is", - "description": "Infer the content-type of a request.", "version": "2.0.1", - "contributors": [ - "Douglas Christopher Wilson ", - "Jonathan Ong (http://jongleberry.com)" + "description": "Infer the content-type of a request.", + "keywords": [ + "content", + "type", + "checking" ], - "license": "MIT", "repository": "jshttp/type-is", "funding": { "type": "opencollective", "url": "https://opencollective.com/express" }, + "license": "MIT", + "contributors": [ + "Douglas Christopher Wilson ", + "Jonathan Ong (http://jongleberry.com)" + ], + "type": "commonjs", + "exports": "./dist/index.js", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "files": [ + "dist/" + ], + "scripts": { + "build": "ts-scripts build", + "format": "ts-scripts format", + "prepare": "ts-scripts install && npm run build", + "specs": "ts-scripts specs", + "test": "ts-scripts test" + }, "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "devDependencies": { - "eslint": "7.32.0", - "eslint-config-standard": "14.1.1", - "eslint-plugin-import": "2.31.0", - "eslint-plugin-markdown": "2.2.1", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-promise": "5.2.0", - "eslint-plugin-standard": "4.1.0", - "mocha": "9.2.2", - "nyc": "15.1.0" + "@borderless/ts-scripts": "^0.15.0", + "@types/media-typer": "^1.1.3", + "@types/mime-types": "^3.0.1", + "@types/node": "^22.13.10", + "@vitest/coverage-v8": "^3.0.5", + "typescript": "^5.7.3", + "vitest": "^3.2.4" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" }, - "files": [ - "LICENSE", - "HISTORY.md", - "index.js" - ], - "scripts": { - "lint": "eslint .", - "test": "mocha --reporter spec --check-leaks --bail test/", - "test:debug": "mocha --reporter spec --check-leaks --inspect --inspect-brk test/", - "test-ci": "nyc --reporter=lcovonly --reporter=text npm test", - "test-cov": "nyc --reporter=html --reporter=text npm test" - }, - "keywords": [ - "content", - "type", - "checking" - ] + "ts-scripts": { + "dist": [ + "dist" + ], + "project": [ + "tsconfig.build.json" + ] + } } diff --git a/src/index.spec.ts b/src/index.spec.ts new file mode 100644 index 0000000..889b066 --- /dev/null +++ b/src/index.spec.ts @@ -0,0 +1,421 @@ +import { describe, it, assert } from "vitest"; +import { is, request, hasBody, normalize, match } from "./index.js"; + +describe("request(req, types)", () => { + it("should ignore params", () => { + const req = createRequest("text/html; charset=utf-8"); + assert.strictEqual(request(req, ["text/*"]), "text/html"); + }); + + it("should ignore params LWS", () => { + const req = createRequest("text/html ; charset=utf-8"); + assert.strictEqual(request(req, ["text/*"]), "text/html"); + }); + + it("should ignore casing", () => { + const req = createRequest("text/HTML"); + assert.strictEqual(request(req, ["text/*"]), "text/html"); + }); + + it("should fail invalid type", () => { + const req = createRequest("text/html**"); + assert.strictEqual(request(req, ["text/*"]), false); + }); + + it("should not match invalid type", () => { + const req = createRequest("text/html"); + assert.strictEqual(request(req, ["text/html/"]), false); + }); + + describe("when no body is given", () => { + it("should return null", () => { + const req = { headers: {} }; + + assert.strictEqual(request(req), false); + assert.strictEqual(request(req, ["image/*"]), false); + }); + }); + + describe("when no content type is given", () => { + it("should return false", () => { + const req = createRequest(); + assert.strictEqual(request(req), false); + assert.strictEqual(request(req, ["image/*"]), false); + assert.strictEqual(request(req, ["text/*", "image/*"]), false); + }); + }); + + describe("give no types", () => { + it("should return the mime type", () => { + const req = createRequest("image/png"); + assert.strictEqual(request(req), "image/png"); + }); + }); + + describe("given one type", () => { + it("should return the type or false", () => { + const req = createRequest("image/png"); + + assert.strictEqual(request(req, ["png"]), "png"); + assert.strictEqual(request(req, [".png"]), ".png"); + assert.strictEqual(request(req, ["image/png"]), "image/png"); + assert.strictEqual(request(req, ["image/*"]), "image/png"); + assert.strictEqual(request(req, ["*/png"]), "image/png"); + + assert.strictEqual(request(req, ["jpeg"]), false); + assert.strictEqual(request(req, [".jpeg"]), false); + assert.strictEqual(request(req, ["image/jpeg"]), false); + assert.strictEqual(request(req, ["text/*"]), false); + assert.strictEqual(request(req, ["*/jpeg"]), false); + + assert.strictEqual(request(req, ["bogus"]), false); + assert.strictEqual(request(req, ["something/bogus*"]), false); + }); + }); + + describe("given multiple types", () => { + it("should return the first match or false", () => { + const req = createRequest("image/png"); + + assert.strictEqual(request(req, ["png"]), "png"); + assert.strictEqual(request(req, [".png"]), ".png"); + assert.strictEqual(request(req, ["text/*", "image/*"]), "image/png"); + assert.strictEqual(request(req, ["image/*", "text/*"]), "image/png"); + assert.strictEqual(request(req, ["image/*", "image/png"]), "image/png"); + assert.strictEqual(request(req, ["image/png", "image/*"]), "image/png"); + + assert.strictEqual(request(req, ["jpeg"]), false); + assert.strictEqual(request(req, [".jpeg"]), false); + assert.strictEqual(request(req, ["text/*", "application/*"]), false); + assert.strictEqual( + request(req, ["text/html", "text/plain", "application/json"]), + false, + ); + }); + }); + + describe("given +suffix", () => { + it("should match suffix types", () => { + const req = createRequest("application/vnd+json"); + + assert.strictEqual(request(req, ["+json"]), "application/vnd+json"); + assert.strictEqual( + request(req, ["application/vnd+json"]), + "application/vnd+json", + ); + assert.strictEqual( + request(req, ["application/*+json"]), + "application/vnd+json", + ); + assert.strictEqual(request(req, ["*/vnd+json"]), "application/vnd+json"); + assert.strictEqual(request(req, ["application/json"]), false); + assert.strictEqual(request(req, ["text/*+json"]), false); + }); + }); + + describe('given "*/*"', () => { + it("should match any content-type", () => { + assert.strictEqual( + request(createRequest("text/html"), ["*/*"]), + "text/html", + ); + assert.strictEqual( + request(createRequest("text/xml"), ["*/*"]), + "text/xml", + ); + assert.strictEqual( + request(createRequest("application/json"), ["*/*"]), + "application/json", + ); + assert.strictEqual( + request(createRequest("application/vnd+json"), ["*/*"]), + "application/vnd+json", + ); + }); + + it("should not match invalid content-type", () => { + assert.strictEqual(request(createRequest("bogus"), ["*/*"]), false); + }); + + it("should not match body-less request", () => { + const req = { headers: { "content-type": "text/html" } }; + assert.strictEqual(request(req, ["*/*"]), false); + }); + }); + + describe("when Content-Type: application/x-www-form-urlencoded", () => { + it('should match "urlencoded"', () => { + const req = createRequest("application/x-www-form-urlencoded"); + + assert.strictEqual(request(req, ["urlencoded"]), "urlencoded"); + assert.strictEqual(request(req, ["json", "urlencoded"]), "urlencoded"); + assert.strictEqual(request(req, ["urlencoded", "json"]), "urlencoded"); + }); + }); + + describe("when Content-Type: multipart/form-data", () => { + it('should match "multipart/*"', () => { + const req = createRequest("multipart/form-data"); + + assert.strictEqual(request(req, ["multipart/*"]), "multipart/form-data"); + }); + + it('should match "multipart"', () => { + const req = createRequest("multipart/form-data"); + + assert.strictEqual(request(req, ["multipart"]), "multipart"); + }); + }); +}); + +describe("hasBody(req)", () => { + describe("content-length", () => { + it("should indicate body", () => { + const req = { headers: { "content-length": "1" } }; + assert.strictEqual(hasBody(req), true); + }); + + it("should be true when 0", () => { + const req = { headers: { "content-length": "0" } }; + assert.strictEqual(hasBody(req), true); + }); + + it("should be false when bogus", () => { + const req = { headers: { "content-length": "bogus" } }; + assert.strictEqual(hasBody(req), false); + }); + }); + + describe("transfer-encoding", () => { + it("should indicate body", () => { + const req = { headers: { "transfer-encoding": "chunked" } }; + assert.strictEqual(hasBody(req), true); + }); + }); +}); + +describe("is(mediaType, types)", () => { + it("should ignore params", () => { + assert.strictEqual(is("text/html; charset=utf-8", ["text/*"]), "text/html"); + }); + + it("should ignore casing", () => { + assert.strictEqual(is("text/HTML", ["text/*"]), "text/html"); + }); + + it("should fail invalid type", () => { + assert.strictEqual(is("text/html**", ["text/*"]), false); + }); + + it("should not match invalid type", () => { + assert.strictEqual(is("text/html", ["text/html/"]), false); + }); + + describe("when no media type is given", () => { + it("should return false", () => { + assert.strictEqual(is("", ["application/json"]), false); + assert.strictEqual(is(null, ["image/*"]), false); + assert.strictEqual(is(undefined, ["text/*", "image/*"]), false); + }); + }); + + describe("given no types", () => { + it("should return the mime type", () => { + assert.strictEqual(is("image/png"), "image/png"); + }); + }); + + describe("given one type", () => { + it("should return the type or false", () => { + assert.strictEqual(is("image/png", ["png"]), "png"); + assert.strictEqual(is("image/png", [".png"]), ".png"); + assert.strictEqual(is("image/png", ["image/png"]), "image/png"); + assert.strictEqual(is("image/png", ["image/*"]), "image/png"); + assert.strictEqual(is("image/png", ["*/png"]), "image/png"); + + assert.strictEqual(is("image/png", ["jpeg"]), false); + assert.strictEqual(is("image/png", [".jpeg"]), false); + assert.strictEqual(is("image/png", ["image/jpeg"]), false); + assert.strictEqual(is("image/png", ["text/*"]), false); + assert.strictEqual(is("image/png", ["*/jpeg"]), false); + + assert.strictEqual(is("image/png", ["bogus"]), false); + assert.strictEqual(is("image/png", ["something/bogus*"]), false); + }); + }); + + describe("given multiple types", () => { + it("should return the first match or false", () => { + assert.strictEqual(is("image/png", ["png"]), "png"); + assert.strictEqual(is("image/png", [".png"]), ".png"); + assert.strictEqual(is("image/png", ["text/*", "image/*"]), "image/png"); + assert.strictEqual(is("image/png", ["image/*", "text/*"]), "image/png"); + assert.strictEqual( + is("image/png", ["image/*", "image/png"]), + "image/png", + ); + assert.strictEqual( + is("image/png", ["image/png", "image/*"]), + "image/png", + ); + + assert.strictEqual(is("image/png", ["jpeg"]), false); + assert.strictEqual(is("image/png", [".jpeg"]), false); + assert.strictEqual(is("image/png", ["text/*", "application/*"]), false); + assert.strictEqual( + is("image/png", ["text/html", "text/plain", "application/json"]), + false, + ); + }); + }); + + describe("given +suffix", () => { + it("should match suffix types", () => { + assert.strictEqual( + is("application/vnd+json", ["+json"]), + "application/vnd+json", + ); + assert.strictEqual( + is("application/vnd+json", ["application/vnd+json"]), + "application/vnd+json", + ); + assert.strictEqual( + is("application/vnd+json", ["application/*+json"]), + "application/vnd+json", + ); + assert.strictEqual( + is("application/vnd+json", ["*/vnd+json"]), + "application/vnd+json", + ); + assert.strictEqual( + is("application/vnd+json", ["application/json"]), + false, + ); + assert.strictEqual(is("application/vnd+json", ["text/*+json"]), false); + }); + }); + + describe('given "*/*"', () => { + it("should match any media type", () => { + assert.strictEqual(is("text/html", ["*/*"]), "text/html"); + assert.strictEqual(is("text/xml", ["*/*"]), "text/xml"); + assert.strictEqual(is("application/json", ["*/*"]), "application/json"); + assert.strictEqual( + is("application/vnd+json", ["*/*"]), + "application/vnd+json", + ); + }); + + it("should not match invalid media type", () => { + assert.strictEqual(is("bogus", ["*/*"]), false); + }); + }); + + describe("when media type is application/x-www-form-urlencoded", () => { + it('should match "urlencoded"', () => { + assert.strictEqual( + is("application/x-www-form-urlencoded", ["urlencoded"]), + "urlencoded", + ); + assert.strictEqual( + is("application/x-www-form-urlencoded", ["json", "urlencoded"]), + "urlencoded", + ); + assert.strictEqual( + is("application/x-www-form-urlencoded", ["urlencoded", "json"]), + "urlencoded", + ); + }); + }); + + describe("when media type is multipart/form-data", () => { + it('should match "multipart/*"', () => { + assert.strictEqual( + is("multipart/form-data", ["multipart/*"]), + "multipart/form-data", + ); + }); + + it('should match "multipart"', () => { + assert.strictEqual(is("multipart/form-data", ["multipart"]), "multipart"); + }); + }); +}); + +describe("match(expected, actual)", () => { + it("should perform exact matching", () => { + assert.strictEqual(match("text/html", "text/html"), true); + assert.strictEqual(match("text/html", "text/plain"), false); + assert.strictEqual(match("text/html", "text/xml"), false); + assert.strictEqual(match("text/html", "application/html"), false); + assert.strictEqual(match("text/html", "text/html+xml"), false); + }); + + it("should perform type wildcard matching", () => { + assert.strictEqual(match("*/html", "text/html"), true); + assert.strictEqual(match("*/html", "application/html"), true); + assert.strictEqual(match("*/html", "text/xml"), false); + assert.strictEqual(match("*/html", "text/html+xml"), false); + }); + + it("should perform subtype wildcard matching", () => { + assert.strictEqual(match("text/*", "text/html"), true); + assert.strictEqual(match("text/*", "text/xml"), true); + assert.strictEqual(match("text/*", "text/html+xml"), true); + assert.strictEqual(match("text/*", "application/xml"), false); + }); + + it("should perform full wildcard matching", () => { + assert.strictEqual(match("*/*", "text/html"), true); + assert.strictEqual(match("*/*", "text/html+xml"), true); + assert.strictEqual(match("*/*+xml", "text/html+xml"), true); + }); + + it("should perform full wildcard matching with specific suffix", () => { + assert.strictEqual(match("*/*+xml", "text/html+xml"), true); + assert.strictEqual(match("*/*+xml", "text/html"), false); + }); +}); + +describe("normalize(type)", () => { + it("should return media type for extension", () => { + assert.strictEqual(normalize("json"), "application/json"); + }); + + it("should return expanded wildcard for suffix", () => { + assert.strictEqual(normalize("+json"), "*/*+json"); + }); + + it("should pass through media type", () => { + assert.strictEqual(normalize("application/json"), "application/json"); + }); + + it("should pass through wildcard", () => { + assert.strictEqual(normalize("*/*"), "*/*"); + assert.strictEqual(normalize("image/*"), "image/*"); + }); + + it("should return empty string for unmapped extension", () => { + assert.strictEqual(normalize("unknown"), ""); + }); + + it('should expand special "urlencoded"', () => { + assert.strictEqual( + normalize("urlencoded"), + "application/x-www-form-urlencoded", + ); + }); + + it('should expand special "multipart"', () => { + assert.strictEqual(normalize("multipart"), "multipart/*"); + }); +}); + +function createRequest(type?: string) { + return { + headers: { + "content-type": type, + "transfer-encoding": "chunked", + }, + }; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f2fc4fd --- /dev/null +++ b/src/index.ts @@ -0,0 +1,121 @@ +/*! + * type-is + * Copyright(c) 2014 Jonathan Ong + * Copyright(c) 2014-2015 Douglas Christopher Wilson + * MIT Licensed + */ + +import { parse } from "content-type"; +import { lookup } from "mime-types"; +import { test } from "media-typer"; + +/** + * Node.js HTTP request shape. + */ +export interface RequestLike { + headers: Record; +} + +/** + * Check if the incoming request contains the "Content-Type" header field, and + * it contains any of the given mime `type`s. If there is no request body or + * content type, `false` is returned. Otherwise, it returns the first `type` + * that matches. + */ +export function request(req: RequestLike, types?: string[]): string | false { + if (!hasBody(req)) return false; + const header = req.headers["content-type"]; + if (!header) return false; + return is(Array.isArray(header) ? header[0] : header, types); +} + +/** + * Check if a request has a request body. A request with a body must either have + * `transfer-encoding` or `content-length` headers set. + */ +export function hasBody(req: RequestLike): boolean { + return ( + req.headers["transfer-encoding"] !== undefined || + !Number.isNaN(Number(req.headers["content-length"])) + ); +} + +/** + * Compare a `value` content-type with `types`. Each `type` can be an extension + * like `html`, a special shortcut like `multipart` or `urlencoded`, or a mime + * type. + * + * If no types match, `false` is returned. Otherwise, the first `type` that + * matches is returned. + */ +export function is( + value: string | undefined | null, + types?: string[], +): string | false { + const val = normalizeType(value); + if (!val) return false; + if (!types || types.length === 0) return val; + + for (const type of types) { + const normalized = normalize(type); + if (match(normalized, val)) { + return type[0] === "+" || type.indexOf("*") !== -1 ? val : type; + } + } + + return false; +} + +/** + * Normalize a mime type. If it's a shorthand, expand it to a valid mime type. + */ +export function normalize(type: string): string { + switch (type) { + case "urlencoded": + return "application/x-www-form-urlencoded"; + case "multipart": + return "multipart/*"; + } + + if (type.startsWith("+")) return `*/*${type}`; + if (type.includes("/")) return type; + return lookup(type) || ""; +} + +/** + * Check if `expected` mime type matches `actual` mime type with wildcard and + * +suffix support. + */ +export function match(expected: string, actual: string): boolean { + const actualParts = actual.split("/"); + const expectedParts = expected.split("/"); + + if (actualParts.length !== 2 || expectedParts.length !== 2) return false; + + if (expectedParts[0] !== "*" && expectedParts[0] !== actualParts[0]) { + return false; + } + + if (expectedParts[1].slice(0, 2) === "*+") { + return ( + expectedParts[1].length <= actualParts[1].length + 1 && + expectedParts[1].slice(1) === + actualParts[1].slice(1 - expectedParts[1].length) + ); + } + + if (expectedParts[1] !== "*" && expectedParts[1] !== actualParts[1]) { + return false; + } + + return true; +} + +/** + * Normalize a type and remove parameters. + */ +function normalizeType(value: string | undefined | null): string | null { + if (!value) return null; + const { type } = parse(value, { parameters: false }); + return test(type) ? type : null; +} diff --git a/test/.eslintrc.yml b/test/.eslintrc.yml deleted file mode 100644 index 9808c3b..0000000 --- a/test/.eslintrc.yml +++ /dev/null @@ -1,2 +0,0 @@ -env: - mocha: true diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 4443196..0000000 --- a/test/test.js +++ /dev/null @@ -1,398 +0,0 @@ - -var assert = require('assert') -var typeis = require('..') - -describe('typeis(req, types)', function () { - it('should ignore params', function () { - var req = createRequest('text/html; charset=utf-8') - assert.strictEqual(typeis(req, ['text/*']), 'text/html') - }) - - it('should ignore params LWS', function () { - var req = createRequest('text/html ; charset=utf-8') - assert.strictEqual(typeis(req, ['text/*']), 'text/html') - }) - - it('should ignore casing', function () { - var req = createRequest('text/HTML') - assert.strictEqual(typeis(req, ['text/*']), 'text/html') - }) - - it('should fail invalid type', function () { - var req = createRequest('text/html**') - assert.strictEqual(typeis(req, ['text/*']), false) - }) - - it('should not match invalid type', function () { - var req = createRequest('text/html') - assert.strictEqual(typeis(req, ['text/html/']), false) - assert.strictEqual(typeis(req, [undefined, null, true, function () {}]), false) - }) - - describe('when no body is given', function () { - it('should return null', function () { - var req = { headers: {} } - - assert.strictEqual(typeis(req), null) - assert.strictEqual(typeis(req, ['image/*']), null) - assert.strictEqual(typeis(req, 'image/*', 'text/*'), null) - }) - }) - - describe('when no content type is given', function () { - it('should return false', function () { - var req = createRequest() - assert.strictEqual(typeis(req), false) - assert.strictEqual(typeis(req, ['image/*']), false) - assert.strictEqual(typeis(req, ['text/*', 'image/*']), false) - }) - }) - - describe('give no types', function () { - it('should return the mime type', function () { - var req = createRequest('image/png') - assert.strictEqual(typeis(req), 'image/png') - }) - }) - - describe('given one type', function () { - it('should return the type or false', function () { - var req = createRequest('image/png') - - assert.strictEqual(typeis(req, ['png']), 'png') - assert.strictEqual(typeis(req, ['.png']), '.png') - assert.strictEqual(typeis(req, ['image/png']), 'image/png') - assert.strictEqual(typeis(req, ['image/*']), 'image/png') - assert.strictEqual(typeis(req, ['*/png']), 'image/png') - - assert.strictEqual(typeis(req, ['jpeg']), false) - assert.strictEqual(typeis(req, ['.jpeg']), false) - assert.strictEqual(typeis(req, ['image/jpeg']), false) - assert.strictEqual(typeis(req, ['text/*']), false) - assert.strictEqual(typeis(req, ['*/jpeg']), false) - - assert.strictEqual(typeis(req, ['bogus']), false) - assert.strictEqual(typeis(req, ['something/bogus*']), false) - }) - }) - - describe('given multiple types', function () { - it('should return the first match or false', function () { - var req = createRequest('image/png') - - assert.strictEqual(typeis(req, ['png']), 'png') - assert.strictEqual(typeis(req, '.png'), '.png') - assert.strictEqual(typeis(req, ['text/*', 'image/*']), 'image/png') - assert.strictEqual(typeis(req, ['image/*', 'text/*']), 'image/png') - assert.strictEqual(typeis(req, ['image/*', 'image/png']), 'image/png') - assert.strictEqual(typeis(req, 'image/png', 'image/*'), 'image/png') - - assert.strictEqual(typeis(req, ['jpeg']), false) - assert.strictEqual(typeis(req, ['.jpeg']), false) - assert.strictEqual(typeis(req, ['text/*', 'application/*']), false) - assert.strictEqual(typeis(req, ['text/html', 'text/plain', 'application/json']), false) - }) - }) - - describe('given +suffix', function () { - it('should match suffix types', function () { - var req = createRequest('application/vnd+json') - - assert.strictEqual(typeis(req, '+json'), 'application/vnd+json') - assert.strictEqual(typeis(req, 'application/vnd+json'), 'application/vnd+json') - assert.strictEqual(typeis(req, 'application/*+json'), 'application/vnd+json') - assert.strictEqual(typeis(req, '*/vnd+json'), 'application/vnd+json') - assert.strictEqual(typeis(req, 'application/json'), false) - assert.strictEqual(typeis(req, 'text/*+json'), false) - }) - }) - - describe('given "*/*"', function () { - it('should match any content-type', function () { - assert.strictEqual(typeis(createRequest('text/html'), '*/*'), 'text/html') - assert.strictEqual(typeis(createRequest('text/xml'), '*/*'), 'text/xml') - assert.strictEqual(typeis(createRequest('application/json'), '*/*'), 'application/json') - assert.strictEqual(typeis(createRequest('application/vnd+json'), '*/*'), 'application/vnd+json') - }) - - it('should not match invalid content-type', function () { - assert.strictEqual(typeis(createRequest('bogus'), '*/*'), false) - }) - - it('should not match body-less request', function () { - var req = { headers: { 'content-type': 'text/html' } } - assert.strictEqual(typeis(req, '*/*'), null) - }) - }) - - describe('when Content-Type: application/x-www-form-urlencoded', function () { - it('should match "urlencoded"', function () { - var req = createRequest('application/x-www-form-urlencoded') - - assert.strictEqual(typeis(req, ['urlencoded']), 'urlencoded') - assert.strictEqual(typeis(req, ['json', 'urlencoded']), 'urlencoded') - assert.strictEqual(typeis(req, ['urlencoded', 'json']), 'urlencoded') - }) - }) - - describe('when Content-Type: multipart/form-data', function () { - it('should match "multipart/*"', function () { - var req = createRequest('multipart/form-data') - - assert.strictEqual(typeis(req, ['multipart/*']), 'multipart/form-data') - }) - - it('should match "multipart"', function () { - var req = createRequest('multipart/form-data') - - assert.strictEqual(typeis(req, ['multipart']), 'multipart') - }) - }) -}) - -describe('typeis.hasBody(req)', function () { - describe('content-length', function () { - it('should indicate body', function () { - var req = { headers: { 'content-length': '1' } } - assert.strictEqual(typeis.hasBody(req), true) - }) - - it('should be true when 0', function () { - var req = { headers: { 'content-length': '0' } } - assert.strictEqual(typeis.hasBody(req), true) - }) - - it('should be false when bogus', function () { - var req = { headers: { 'content-length': 'bogus' } } - assert.strictEqual(typeis.hasBody(req), false) - }) - }) - - describe('transfer-encoding', function () { - it('should indicate body', function () { - var req = { headers: { 'transfer-encoding': 'chunked' } } - assert.strictEqual(typeis.hasBody(req), true) - }) - }) -}) - -describe('typeis.is(mediaType, types)', function () { - it('should ignore params', function () { - assert.strictEqual(typeis.is('text/html; charset=utf-8', ['text/*']), - 'text/html') - }) - - it('should ignore casing', function () { - assert.strictEqual(typeis.is('text/HTML', ['text/*']), 'text/html') - }) - - it('should fail invalid type', function () { - assert.strictEqual(typeis.is('text/html**', ['text/*']), false) - }) - - it('should not match invalid type', function () { - var req = createRequest('text/html') - assert.strictEqual(typeis(req, ['text/html/']), false) - assert.strictEqual(typeis(req, [undefined, null, true, function () {}]), false) - }) - - it('should not match invalid type', function () { - assert.strictEqual(typeis.is('text/html', ['text/html/']), false) - assert.strictEqual(typeis.is('text/html', [undefined, null, true, function () {}]), false) - }) - - describe('when no media type is given', function () { - it('should return false', function () { - assert.strictEqual(typeis.is(), false) - assert.strictEqual(typeis.is('', ['application/json']), false) - assert.strictEqual(typeis.is(null, ['image/*']), false) - assert.strictEqual(typeis.is(undefined, ['text/*', 'image/*']), false) - }) - }) - - describe('given no types', function () { - it('should return the mime type', function () { - assert.strictEqual(typeis.is('image/png'), 'image/png') - }) - }) - - describe('given one type', function () { - it('should return the type or false', function () { - assert.strictEqual(typeis.is('image/png', ['png']), 'png') - assert.strictEqual(typeis.is('image/png', ['.png']), '.png') - assert.strictEqual(typeis.is('image/png', ['image/png']), 'image/png') - assert.strictEqual(typeis.is('image/png', ['image/*']), 'image/png') - assert.strictEqual(typeis.is('image/png', ['*/png']), 'image/png') - - assert.strictEqual(typeis.is('image/png', ['jpeg']), false) - assert.strictEqual(typeis.is('image/png', ['.jpeg']), false) - assert.strictEqual(typeis.is('image/png', ['image/jpeg']), false) - assert.strictEqual(typeis.is('image/png', ['text/*']), false) - assert.strictEqual(typeis.is('image/png', ['*/jpeg']), false) - - assert.strictEqual(typeis.is('image/png', ['bogus']), false) - assert.strictEqual(typeis.is('image/png', ['something/bogus*']), false) - }) - }) - - describe('given multiple types', function () { - it('should return the first match or false', function () { - assert.strictEqual(typeis.is('image/png', ['png']), 'png') - assert.strictEqual(typeis.is('image/png', '.png'), '.png') - assert.strictEqual(typeis.is('image/png', ['text/*', 'image/*']), 'image/png') - assert.strictEqual(typeis.is('image/png', ['image/*', 'text/*']), 'image/png') - assert.strictEqual(typeis.is('image/png', ['image/*', 'image/png']), 'image/png') - assert.strictEqual(typeis.is('image/png', 'image/png', 'image/*'), 'image/png') - - assert.strictEqual(typeis.is('image/png', ['jpeg']), false) - assert.strictEqual(typeis.is('image/png', ['.jpeg']), false) - assert.strictEqual(typeis.is('image/png', ['text/*', 'application/*']), false) - assert.strictEqual(typeis.is('image/png', ['text/html', 'text/plain', 'application/json']), false) - }) - }) - - describe('given +suffix', function () { - it('should match suffix types', function () { - assert.strictEqual(typeis.is('application/vnd+json', '+json'), 'application/vnd+json') - assert.strictEqual(typeis.is('application/vnd+json', 'application/vnd+json'), 'application/vnd+json') - assert.strictEqual(typeis.is('application/vnd+json', 'application/*+json'), 'application/vnd+json') - assert.strictEqual(typeis.is('application/vnd+json', '*/vnd+json'), 'application/vnd+json') - assert.strictEqual(typeis.is('application/vnd+json', 'application/json'), false) - assert.strictEqual(typeis.is('application/vnd+json', 'text/*+json'), false) - }) - }) - - describe('given "*/*"', function () { - it('should match any media type', function () { - assert.strictEqual(typeis.is('text/html', '*/*'), 'text/html') - assert.strictEqual(typeis.is('text/xml', '*/*'), 'text/xml') - assert.strictEqual(typeis.is('application/json', '*/*'), 'application/json') - assert.strictEqual(typeis.is('application/vnd+json', '*/*'), 'application/vnd+json') - }) - - it('should not match invalid media type', function () { - assert.strictEqual(typeis.is('bogus', '*/*'), false) - }) - }) - - describe('when media type is application/x-www-form-urlencoded', function () { - it('should match "urlencoded"', function () { - assert.strictEqual(typeis.is('application/x-www-form-urlencoded', ['urlencoded']), 'urlencoded') - assert.strictEqual(typeis.is('application/x-www-form-urlencoded', ['json', 'urlencoded']), 'urlencoded') - assert.strictEqual(typeis.is('application/x-www-form-urlencoded', ['urlencoded', 'json']), 'urlencoded') - }) - }) - - describe('when media type is multipart/form-data', function () { - it('should match "multipart/*"', function () { - assert.strictEqual(typeis.is('multipart/form-data', ['multipart/*']), 'multipart/form-data') - }) - - it('should match "multipart"', function () { - assert.strictEqual(typeis.is('multipart/form-data', ['multipart']), 'multipart') - }) - }) - - describe('when give request object', function () { - it('should use the content-type header', function () { - var req = createRequest('image/png') - - assert.strictEqual(typeis.is(req, ['png']), 'png') - assert.strictEqual(typeis.is(req, ['jpeg']), false) - }) - - it('should not check for body', function () { - var req = { headers: { 'content-type': 'text/html' } } - - assert.strictEqual(typeis.is(req, ['html']), 'html') - assert.strictEqual(typeis.is(req, ['jpeg']), false) - }) - }) -}) - -describe('typeis.match(expected, actual)', function () { - it('should return false when expected is false', function () { - assert.strictEqual(typeis.match(false, 'text/html'), false) - }) - - it('should perform exact matching', function () { - assert.strictEqual(typeis.match('text/html', 'text/html'), true) - assert.strictEqual(typeis.match('text/html', 'text/plain'), false) - assert.strictEqual(typeis.match('text/html', 'text/xml'), false) - assert.strictEqual(typeis.match('text/html', 'application/html'), false) - assert.strictEqual(typeis.match('text/html', 'text/html+xml'), false) - }) - - it('should perform type wildcard matching', function () { - assert.strictEqual(typeis.match('*/html', 'text/html'), true) - assert.strictEqual(typeis.match('*/html', 'application/html'), true) - assert.strictEqual(typeis.match('*/html', 'text/xml'), false) - assert.strictEqual(typeis.match('*/html', 'text/html+xml'), false) - }) - - it('should perform subtype wildcard matching', function () { - assert.strictEqual(typeis.match('text/*', 'text/html'), true) - assert.strictEqual(typeis.match('text/*', 'text/xml'), true) - assert.strictEqual(typeis.match('text/*', 'text/html+xml'), true) - assert.strictEqual(typeis.match('text/*', 'application/xml'), false) - }) - - it('should perform full wildcard matching', function () { - assert.strictEqual(typeis.match('*/*', 'text/html'), true) - assert.strictEqual(typeis.match('*/*', 'text/html+xml'), true) - assert.strictEqual(typeis.match('*/*+xml', 'text/html+xml'), true) - }) - - it('should perform full wildcard matching with specific suffix', function () { - assert.strictEqual(typeis.match('*/*+xml', 'text/html+xml'), true) - assert.strictEqual(typeis.match('*/*+xml', 'text/html'), false) - }) -}) - -describe('typeis.normalize(type)', function () { - it('should return false for non-strings', function () { - assert.strictEqual(typeis.normalize({}), false) - assert.strictEqual(typeis.normalize([]), false) - assert.strictEqual(typeis.normalize(42), false) - assert.strictEqual(typeis.normalize(null), false) - assert.strictEqual(typeis.normalize(function () {}), false) - }) - - it('should return media type for extension', function () { - assert.strictEqual(typeis.normalize('json'), 'application/json') - }) - - it('should return expanded wildcard for suffix', function () { - assert.strictEqual(typeis.normalize('+json'), '*/*+json') - }) - - it('should pass through media type', function () { - assert.strictEqual(typeis.normalize('application/json'), 'application/json') - }) - - it('should pass through wildcard', function () { - assert.strictEqual(typeis.normalize('*/*'), '*/*') - assert.strictEqual(typeis.normalize('image/*'), 'image/*') - }) - - it('should return false for unmapped extension', function () { - assert.strictEqual(typeis.normalize('unknown'), false) - }) - - it('should expand special "urlencoded"', function () { - assert.strictEqual(typeis.normalize('urlencoded'), 'application/x-www-form-urlencoded') - }) - - it('should expand special "multipart"', function () { - assert.strictEqual(typeis.normalize('multipart'), 'multipart/*') - }) -}) - -function createRequest (type) { - return { - headers: { - 'content-type': type || undefined, - 'transfer-encoding': 'chunked' - } - } -} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..80186ba --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "exclude": ["src/**/*.spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1f3b68c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@borderless/ts-scripts/configs/tsconfig.json", + "compilerOptions": { + "target": "ES2022", + "rootDir": "src", + "outDir": "dist", + "module": "nodenext", + "moduleResolution": "nodenext", + "types": ["node"] + }, + "include": ["src/**/*"] +}