Skip to content
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
15 changes: 2 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,26 @@ $ npm install media-typer

## API

<!-- eslint-disable no-unused-vars -->

```js
var typer = require("media-typer");
```

### typer.parse(string)

<!-- eslint-disable no-undef, no-unused-vars -->

```js
var obj = typer.parse("image/svg+xml");
```

Parse a media type string. This will return an object with the following
properties (examples are shown for the string `'image/svg+xml; charset=utf-8'`):
Parse a media type string. This will return an object with the following properties:

- `type`: The type of the media type (always lower case). Example: `'image'`

- `subtype`: The subtype of the media type (always lower case). Example: `'svg'`

- `suffix`: The suffix of the media type (always lower case). Example: `'xml'`
- `suffix`: Optional suffix of the media type (always lower case). Example: `'xml'`

If the given type string is invalid, then a `TypeError` is thrown.

### typer.format(obj)

<!-- eslint-disable no-undef, no-unused-vars -->

```js
var obj = typer.format({ type: "image", subtype: "svg", suffix: "xml" });
```
Expand All @@ -68,8 +59,6 @@ If any of the given object values are invalid, then a `TypeError` is thrown.

### typer.test(string)

<!-- eslint-disable no-undef, no-unused-vars -->

```js
var valid = typer.test("image/svg+xml");
```
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dist/"
],
"scripts": {
"bench": "vitest bench",
"build": "ts-scripts build",
"format": "ts-scripts format",
"prepare": "ts-scripts install",
Expand Down
40 changes: 40 additions & 0 deletions src/index.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { bench, describe } from "vitest";
import * as typer from "./index.js";

describe("typer.format", () => {
bench("basic type", () => {
typer.format({ type: "text", subtype: "html" });
});

bench("type with suffix", () => {
typer.format({ type: "image", subtype: "svg", suffix: "xml" });
});
});

describe("typer.parse", () => {
bench("basic type", () => {
typer.parse("text/html");
});

bench("type with suffix", () => {
typer.parse("image/svg+xml");
});

bench("upper-case type with suffix", () => {
typer.parse("IMAGE/SVG+XML");
});
});

describe("typer.test", () => {
bench("basic type", () => {
typer.test("text/html");
});

bench("type with suffix", () => {
typer.test("image/svg+xml");
});

bench("invalid type", () => {
typer.test("text/plain,wrong");
});
});
50 changes: 24 additions & 26 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,38 +25,45 @@ describe("typer.format", () => {
expect(str).toBe("image/svg+xml");
});

it("should require argument", () => {
expect(() => typer.format(undefined as never)).toThrow(/obj.*required/);
it("should maintain case", () => {
const str = typer.format({ type: "Text", subtype: "Html" });
expect(str).toBe("Text/Html");
});

it("should reject non-objects", () => {
expect(() => typer.format(7 as never)).toThrow(/obj.*required/);
it("should allow + in subtype", () => {
const str = typer.format({ type: "application", subtype: "vnd.api+json" });
expect(str).toBe("application/vnd.api+json");
});

it("should require type", () => {
expect(() => typer.format({} as never)).toThrow(/invalid type/);
expect(() => typer.format({} as never)).toThrow(/Invalid type/);
});

it("should reject invalid type", () => {
expect(() => typer.format({ type: "text/", subtype: "html" })).toThrow(
/invalid type/,
/Invalid type/,
);
});

it("should require subtype", () => {
expect(() => typer.format({ type: "text" } as never)).toThrow(
/invalid subtype/,
/Invalid subtype/,
);
});

it("should reject invalid subtype", () => {
const obj = { type: "text", subtype: "html/" };
expect(() => typer.format(obj)).toThrow(/invalid subtype/);
expect(() => typer.format(obj)).toThrow(/Invalid subtype/);
});

it("should reject empty suffix", () => {
const obj = { type: "text", subtype: "html", suffix: "" };
expect(() => typer.format(obj)).toThrow(/Invalid suffix/);
});

it("should reject invalid suffix", () => {
const obj = { type: "image", subtype: "svg", suffix: "xml\\" };
expect(() => typer.format(obj)).toThrow(/invalid suffix/);
expect(() => typer.format(obj)).toThrow(/Invalid suffix/);
});
});

Expand All @@ -81,19 +88,18 @@ describe("typer.parse", () => {
expect(type.suffix).toBe("xml");
});

it("should parse with multiple + in subtype", () => {
const type = typer.parse("application/vnd.api+json+gzip");
expect(type.type).toBe("application");
expect(type.subtype).toBe("vnd.api+json");
expect(type.suffix).toBe("gzip");
});

invalidTypes.forEach((type) => {
it(`should throw on invalid media type ${JSON.stringify(type)}`, () => {
expect(() => typer.parse(type)).toThrow(/invalid media type/);
expect(() => typer.parse(type)).toThrow(/Invalid media type/);
});
});

it("should require argument", () => {
expect(() => typer.parse(undefined as never)).toThrow(/string.*required/);
});

it("should reject non-strings", () => {
expect(() => typer.parse(7 as never)).toThrow(/string.*required/);
});
});

describe("typer.test", () => {
Expand All @@ -114,12 +120,4 @@ describe("typer.test", () => {
expect(typer.test(type)).toBe(false);
});
});

it("should require argument", () => {
expect(() => typer.test(undefined as never)).toThrow(/string.*required/);
});

it("should reject non-strings", () => {
expect(() => typer.test(7 as never)).toThrow(/string.*required/);
});
});
49 changes: 13 additions & 36 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
* ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
* DIGIT = %x30-39 ; 0-9
*/
const subtypeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$/;
const subtypeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}$/;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed we allow multiple + in the typeRegExp but not when building it which breaks the ability to round trip. The RFC also allows multiple +.

const typeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126}$/;
const typeRegExp =
/^ *([A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126})\/([A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}) *$/;
/^[A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126}\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}$/;

/**
* Media type object.
Expand All @@ -41,27 +41,21 @@ export interface MediaType {
* Format object to media type.
*/
export function format(obj: MediaType): string {
if (!obj || typeof obj !== "object") {
throw new TypeError("argument obj is required");
}

const subtype = obj.subtype;
const suffix = obj.suffix;
const type = obj.type;
const { type, subtype, suffix } = obj;

if (!type || !typeNameRegExp.test(type)) {
throw new TypeError("invalid type");
throw new TypeError(`Invalid type: ${type}`);
}

if (!subtype || !subtypeNameRegExp.test(subtype)) {
throw new TypeError("invalid subtype");
throw new TypeError(`Invalid subtype: ${subtype}`);
}

let str = type + "/" + subtype;

if (suffix) {
if (suffix !== undefined) {
if (!typeNameRegExp.test(suffix)) {
throw new TypeError("invalid suffix");
throw new TypeError(`Invalid suffix: ${suffix}`);
}

str += "+" + suffix;
Expand All @@ -74,22 +68,13 @@ export function format(obj: MediaType): string {
* Parse media type to object.
*/
export function parse(str: string): MediaType {
if (!str) {
throw new TypeError("argument string is required");
}

if (typeof str !== "string") {
throw new TypeError("argument string is required to be a string");
if (!typeRegExp.test(str)) {
throw new TypeError(`Invalid media type: ${str}`);
}

const match = typeRegExp.exec(str.toLowerCase());

if (!match) {
throw new TypeError("invalid media type");
}

const type = match[1];
let subtype = match[2];
const slashIndex = str.indexOf("/");
const type = str.slice(0, slashIndex).toLowerCase();
let subtype = str.slice(slashIndex + 1).toLowerCase();
let suffix: string | undefined;

const index = subtype.lastIndexOf("+");
Expand All @@ -105,13 +90,5 @@ export function parse(str: string): MediaType {
* Test media type.
*/
export function test(str: string): boolean {
if (!str) {
throw new TypeError("argument string is required");
}

if (typeof str !== "string") {
throw new TypeError("argument string is required to be a string");
}

return typeRegExp.test(str.toLowerCase());
return typeRegExp.test(str);
}
Loading