@jsonapi-serde/server 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2025-present, Ben Scholzen 'DASPRiD'
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # JSON:API Serde - Server
2
+
3
+ [![Test](https://github.com/DASPRiD/jsonapi-serde-js/actions/workflows/test.yml/badge.svg)](https://github.com/DASPRiD/jsonapi-serde-js/actions/workflows/test.yml)
4
+ [![codecov](https://codecov.io/gh/DASPRiD/jsonapi-serde-js/graph/badge.svg?token=UfRUzGqiN3)](https://codecov.io/gh/DASPRiD/jsonapi-serde-js)
5
+
6
+ ---
7
+
8
+ JSON:API Serde is a framework-agnostic [JSON:API](https://jsonapi.org) serialization and deserialization library.
9
+
10
+ ## Documentation
11
+
12
+ To check out docs, visit [jsonapi-serde.js.org](https://jsonapi-serde.js.org).
13
+
14
+ ## Changelog
15
+
16
+ Detailed changes for each release are documented in the [CHANGELOG](https://github.com/dasprid/jsonapi-serde-js/blob/main/packages/server/CHANGELOG.md)
17
+
18
+ ## License
19
+
20
+ [BSD-3-Clause](https://github.com/dasprid/jsonapi-serde-js/blob/main/LICENSE)
21
+
22
+ Copyright (c) 2025-present, Ben Scholzen 'DASPRiD'
@@ -0,0 +1,53 @@
1
+ import type { $ZodIssue } from "zod/v4/core";
2
+ import type { JsonApiErrorObject } from "./json-api.js";
3
+ import { JsonApiDocument } from "./response.js";
4
+ /**
5
+ * A JSON:API-compliant error wrapper
6
+ *
7
+ * Accepts one or more `JsonApiErrorObject` instances and provides status inference and document transformation.
8
+ */
9
+ export declare class JsonApiError {
10
+ readonly errors: JsonApiErrorObject[];
11
+ readonly status: number;
12
+ /**
13
+ * Creates a new `JsonApiError` from one or more error objects
14
+ *
15
+ * Automatically infers a suitable HTTP status code.
16
+ *
17
+ * @throws {Error} when no errors are supplied
18
+ */
19
+ constructor(errors: JsonApiErrorObject | JsonApiErrorObject[]);
20
+ /**
21
+ * Determines the appropriate HTTP status code from the error set
22
+ *
23
+ * Falls back to 500 or 400 based on presence and severity of error codes.
24
+ * A warning is emitted when no status codes were defined.
25
+ */
26
+ private static determineStatusCode;
27
+ /**
28
+ * Converts the error into a full JSON:API error document
29
+ */
30
+ toDocument(): JsonApiDocument;
31
+ }
32
+ /**
33
+ * Structured metadata that can be attached to a Zod issue to influence JSON:API output
34
+ */
35
+ export declare class ZodValidationErrorParams {
36
+ /** Custom error code to override Zod’s default for custom errors */
37
+ readonly code: string;
38
+ /** Optional detailed explanation of the error */
39
+ readonly detail: string | undefined;
40
+ /** Optional HTTP status code (e.g., 400 or 422) */
41
+ readonly status: number | undefined;
42
+ constructor(code: string, detail?: string, status?: number);
43
+ }
44
+ /**
45
+ * Translates Zod validation issues into a JSON:API error document
46
+ */
47
+ export declare class ZodValidationError extends JsonApiError {
48
+ constructor(errors: $ZodIssue[], source: "query" | "body");
49
+ /**
50
+ * Resolves the `source` field of a JSON:API error based on Zod’s path
51
+ */
52
+ private static getSource;
53
+ }
@@ -0,0 +1,114 @@
1
+ import { JsonApiDocument } from "./response.js";
2
+ /**
3
+ * A JSON:API-compliant error wrapper
4
+ *
5
+ * Accepts one or more `JsonApiErrorObject` instances and provides status inference and document transformation.
6
+ */
7
+ export class JsonApiError {
8
+ errors;
9
+ status;
10
+ /**
11
+ * Creates a new `JsonApiError` from one or more error objects
12
+ *
13
+ * Automatically infers a suitable HTTP status code.
14
+ *
15
+ * @throws {Error} when no errors are supplied
16
+ */
17
+ constructor(errors) {
18
+ this.errors = Array.isArray(errors) ? errors : [errors];
19
+ if (this.errors.length === 0) {
20
+ throw new Error("At least one error must be supplied");
21
+ }
22
+ this.status = JsonApiError.determineStatusCode(this.errors);
23
+ }
24
+ /**
25
+ * Determines the appropriate HTTP status code from the error set
26
+ *
27
+ * Falls back to 500 or 400 based on presence and severity of error codes.
28
+ * A warning is emitted when no status codes were defined.
29
+ */
30
+ static determineStatusCode = (errors) => {
31
+ const uniqueStatusCodes = [
32
+ ...new Set(errors
33
+ .map((error) => error.status)
34
+ .filter((value) => value !== undefined)
35
+ .map((value) => Number.parseInt(value, 10))),
36
+ ];
37
+ if (uniqueStatusCodes.length === 0) {
38
+ console.warn("No error contained a status code, falling back to 500");
39
+ return 500;
40
+ }
41
+ if (uniqueStatusCodes.length === 1) {
42
+ return uniqueStatusCodes[0];
43
+ }
44
+ if (uniqueStatusCodes.some((statusCode) => statusCode >= 500 && statusCode < 600)) {
45
+ return 500;
46
+ }
47
+ return 400;
48
+ };
49
+ /**
50
+ * Converts the error into a full JSON:API error document
51
+ */
52
+ toDocument() {
53
+ return new JsonApiDocument({
54
+ errors: this.errors,
55
+ }, this.status);
56
+ }
57
+ }
58
+ /**
59
+ * Structured metadata that can be attached to a Zod issue to influence JSON:API output
60
+ */
61
+ export class ZodValidationErrorParams {
62
+ /** Custom error code to override Zod’s default for custom errors */
63
+ code;
64
+ /** Optional detailed explanation of the error */
65
+ detail;
66
+ /** Optional HTTP status code (e.g., 400 or 422) */
67
+ status;
68
+ constructor(code, detail, status) {
69
+ this.code = code;
70
+ this.detail = detail;
71
+ this.status = status;
72
+ }
73
+ }
74
+ /**
75
+ * Translates Zod validation issues into a JSON:API error document
76
+ */
77
+ export class ZodValidationError extends JsonApiError {
78
+ constructor(errors, source) {
79
+ super(errors.map((error) => {
80
+ const params = error.code === "custom" && error.params instanceof ZodValidationErrorParams
81
+ ? error.params
82
+ : null;
83
+ const { code, input, path, message, ...rest } = error;
84
+ const meta = params
85
+ ? Object.fromEntries(Object.entries(rest).filter(([key]) => key !== "params"))
86
+ : rest;
87
+ return {
88
+ status: params?.status?.toString() ?? (source === "query" ? "400" : "422"),
89
+ code: params?.code ?? code,
90
+ title: message,
91
+ detail: params?.detail,
92
+ source: ZodValidationError.getSource(source, path),
93
+ meta: Object.keys(meta).length > 0 ? meta : undefined,
94
+ };
95
+ }));
96
+ }
97
+ /**
98
+ * Resolves the `source` field of a JSON:API error based on Zod’s path
99
+ */
100
+ static getSource(errorSource, path) {
101
+ if (errorSource === "body") {
102
+ return { pointer: `/${path.join("/")}` };
103
+ }
104
+ if (path.length === 0) {
105
+ return undefined;
106
+ }
107
+ return {
108
+ parameter: `${path[0].toString()}${path
109
+ .slice(1)
110
+ .map((element) => `[${element.toString}]`)
111
+ .join()}`,
112
+ };
113
+ }
114
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./error.js";
2
+ export * from "./json-api.js";
3
+ export * from "./response.js";
@@ -0,0 +1,3 @@
1
+ export * from "./error.js";
2
+ export * from "./json-api.js";
3
+ export * from "./response.js";
@@ -0,0 +1,94 @@
1
+ /** Arbitrary metadata object allowed anywhere in a JSON:API document */
2
+ export type Meta = Record<string, unknown>;
3
+ /** A link object with optional attributes for rich linking information */
4
+ export type LinkObject = {
5
+ href: string;
6
+ rel?: string;
7
+ describedby?: string;
8
+ title?: string;
9
+ type?: string;
10
+ hreflang?: string;
11
+ meta?: Meta;
12
+ };
13
+ /** A link can be a plain string URL or a full `LinkObject` */
14
+ export type Link = LinkObject | string;
15
+ /** A collection of named links. Keys are optional and can be nullified */
16
+ export type Links<TKey extends string = string> = Partial<Record<TKey, Link | null>>;
17
+ /** Standard top-level links as defined by JSON:API */
18
+ export type TopLevelLinks = Links<"self" | "related" | "describedby" | "first" | "last" | "prev" | "next">;
19
+ /** A JSON:API-compliant error object */
20
+ export type JsonApiErrorObject = {
21
+ id?: string;
22
+ links?: Links<"about" | "type">;
23
+ status?: string;
24
+ code?: string;
25
+ title?: string;
26
+ detail?: string;
27
+ source?: {
28
+ pointer?: string;
29
+ parameter?: string;
30
+ header?: string;
31
+ };
32
+ meta?: Meta;
33
+ };
34
+ export type JsonApiImplementation = {
35
+ version?: string;
36
+ ext?: string[];
37
+ profile?: string[];
38
+ meta?: Meta;
39
+ };
40
+ /** Optional fields allowed at the top level of a JSON:API document */
41
+ type OptionalTopLevelMembers = {
42
+ links?: TopLevelLinks;
43
+ included?: Resource[];
44
+ };
45
+ /** A data document contains a primary resource (`data`) and may include `meta` */
46
+ type DataTopLevelMembers = {
47
+ data: Resource | Resource[] | null;
48
+ errors?: undefined;
49
+ meta?: Meta;
50
+ };
51
+ /** An error document includes one or more errors, but no primary data */
52
+ type ErrorTopLevelMembers = {
53
+ data?: undefined;
54
+ errors: JsonApiErrorObject[];
55
+ meta?: Meta;
56
+ };
57
+ /** A meta-only document contains only `meta`, no data or errors */
58
+ type MetaTopLevelMembers = {
59
+ data?: undefined;
60
+ errors?: undefined;
61
+ meta: Meta;
62
+ };
63
+ /**
64
+ * The top-level members of a JSON:API document
65
+ *
66
+ * One of `data`, `errors`, or `meta` must be present.
67
+ */
68
+ export type TopLevelMembers = OptionalTopLevelMembers & (DataTopLevelMembers | ErrorTopLevelMembers | MetaTopLevelMembers);
69
+ /** Resource attributes are key-value pairs */
70
+ export type Attributes = Record<string, unknown>;
71
+ /** A map of relationship names to their definitions */
72
+ export type Relationships = Record<string, Relationship>;
73
+ /** A reference to another resource */
74
+ export type ResourceIdentifier = {
75
+ type: string;
76
+ id: string;
77
+ meta?: Meta;
78
+ };
79
+ /** A relationship describes links and/or resource identifiers */
80
+ export type Relationship = {
81
+ data?: ResourceIdentifier | ResourceIdentifier[] | null;
82
+ links?: Links<"self" | "related" | string>;
83
+ meta?: Meta;
84
+ };
85
+ /** A full resource object in the JSON:API format */
86
+ export type Resource = {
87
+ id: string;
88
+ type: string;
89
+ attributes?: Partial<Attributes>;
90
+ relationships?: Partial<Relationships>;
91
+ links?: Links<"self" | string>;
92
+ meta?: Meta;
93
+ };
94
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import type { JsonApiMediaType } from "../http/index.js";
2
+ import type { JsonApiImplementation, TopLevelMembers } from "./json-api.js";
3
+ type MediaTypeOptions = {
4
+ extensions?: string[];
5
+ profiles?: string[];
6
+ };
7
+ /**
8
+ * Represents a JSON:API-compliant HTTP response document
9
+ *
10
+ * Encapsulates status, headers, and body generation.
11
+ */
12
+ export declare class JsonApiDocument {
13
+ private readonly members;
14
+ private readonly mediaTypeOptions;
15
+ private readonly status;
16
+ /**
17
+ * Constructs a new JSON:API response document
18
+ */
19
+ constructor(members: TopLevelMembers, status?: number, mediaTypeOptions?: MediaTypeOptions);
20
+ /**
21
+ * Returns the HTTP status code for this response
22
+ */
23
+ getStatus(): number;
24
+ /**
25
+ * Returns the top-level JSON:API body object
26
+ */
27
+ getBody(): TopLevelMembers & {
28
+ jsonapi: JsonApiImplementation;
29
+ };
30
+ /**
31
+ * Returns the full `Content-Type` header for this response
32
+ *
33
+ * Automatically includes `ext` and `profile` parameters if present in `jsonapi`.
34
+ */
35
+ getContentType(): string;
36
+ /**
37
+ * Verifies that the client accepts the content type of this document
38
+ *
39
+ * @throws {JsonApiError} if content type is not acceptable
40
+ */
41
+ verifyAcceptMediaType(acceptableTypes: JsonApiMediaType[]): void;
42
+ }
43
+ export {};
@@ -0,0 +1,78 @@
1
+ import { JsonApiError } from "./error.js";
2
+ /**
3
+ * Represents a JSON:API-compliant HTTP response document
4
+ *
5
+ * Encapsulates status, headers, and body generation.
6
+ */
7
+ export class JsonApiDocument {
8
+ members;
9
+ mediaTypeOptions;
10
+ status;
11
+ /**
12
+ * Constructs a new JSON:API response document
13
+ */
14
+ constructor(members, status = 200, mediaTypeOptions) {
15
+ this.members = members;
16
+ this.mediaTypeOptions = mediaTypeOptions;
17
+ this.status = status;
18
+ }
19
+ /**
20
+ * Returns the HTTP status code for this response
21
+ */
22
+ getStatus() {
23
+ return this.status;
24
+ }
25
+ /**
26
+ * Returns the top-level JSON:API body object
27
+ */
28
+ getBody() {
29
+ return {
30
+ jsonapi: {
31
+ version: "1.1",
32
+ ext: this.mediaTypeOptions?.extensions,
33
+ profile: this.mediaTypeOptions?.profiles,
34
+ },
35
+ ...this.members,
36
+ };
37
+ }
38
+ /**
39
+ * Returns the full `Content-Type` header for this response
40
+ *
41
+ * Automatically includes `ext` and `profile` parameters if present in `jsonapi`.
42
+ */
43
+ getContentType() {
44
+ const contentType = "application/vnd.api+json";
45
+ const parameters = [];
46
+ if (this.mediaTypeOptions?.extensions && this.mediaTypeOptions.extensions.length > 0) {
47
+ parameters.push(`ext="${this.mediaTypeOptions.extensions.join(" ")}"`);
48
+ }
49
+ if (this.mediaTypeOptions?.profiles && this.mediaTypeOptions.profiles.length > 0) {
50
+ parameters.push(`profile="${this.mediaTypeOptions.profiles.join(" ")}"`);
51
+ }
52
+ if (parameters.length === 0) {
53
+ return contentType;
54
+ }
55
+ return `${contentType};${parameters.join(";")}`;
56
+ }
57
+ /**
58
+ * Verifies that the client accepts the content type of this document
59
+ *
60
+ * @throws {JsonApiError} if content type is not acceptable
61
+ */
62
+ verifyAcceptMediaType(acceptableTypes) {
63
+ const appliedExtensions = this.mediaTypeOptions?.extensions;
64
+ const matchingTypes = acceptableTypes.filter((type) => {
65
+ return type.ext.every((extension) => appliedExtensions?.includes(extension));
66
+ });
67
+ if (matchingTypes.length > 0) {
68
+ return;
69
+ }
70
+ throw new JsonApiError({
71
+ status: "406",
72
+ code: "not_acceptable",
73
+ title: "Not Acceptable",
74
+ detail: "No valid accept types provided, you must accept application/vnd.api+json",
75
+ meta: appliedExtensions ? { appliedExtensions } : undefined,
76
+ });
77
+ }
78
+ }
@@ -0,0 +1,5 @@
1
+ export type Identity<T> = T;
2
+ export type Flatten<T> = Identity<{
3
+ [K in keyof T]: T[K];
4
+ }>;
5
+ export type ParentPaths<T extends string> = T extends `${infer Head}.${infer Tail}` ? Head | `${Head}.${ParentPaths<Tail>}` : T;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import { MediaTypeParser } from "./media-type-parser.js";
2
+ /**
3
+ * Parsed representation of a single media type in the Accept header
4
+ */
5
+ export type ParsedAcceptMediaType = {
6
+ type: string;
7
+ subType: string;
8
+ parameters: Record<string, string>;
9
+ weight: number;
10
+ acceptExt: Record<string, string>;
11
+ };
12
+ /**
13
+ * List of all parsed media types sorted by descending quality (`q`) weight
14
+ */
15
+ export type ParsedAccept = ParsedAcceptMediaType[];
16
+ /**
17
+ * Parses and represents an HTTP `Accept` header as structured data
18
+ */
19
+ export declare class AcceptParser extends MediaTypeParser {
20
+ private static readonly default;
21
+ private static readonly weightRegexp;
22
+ /**
23
+ * Parses the Accept header string into a structured `ParsedAccept` array
24
+ *
25
+ * @throws {MediaTypeParserError} when parsing of Accept header fails
26
+ */
27
+ static parse(header: string): ParsedAccept;
28
+ /**
29
+ * Parses the header into an ordered list of media types
30
+ */
31
+ private process;
32
+ private readMediaType;
33
+ private readSeparator;
34
+ }
35
+ /**
36
+ * Represents a parsed and filtered JSON:API media type with `ext` and `profile` parameters
37
+ */
38
+ export type JsonApiMediaType = {
39
+ ext: string[];
40
+ profile: string[];
41
+ };
42
+ /**
43
+ * Filters and extracts valid JSON:API media types from an `Accept` header
44
+ *
45
+ * Only includes entries with:
46
+ *
47
+ * - `type` of `application` or `*`
48
+ * - `subType` of `vnd.api+json` or `*`
49
+ * - No extra parameters beyond `ext` and `profile`
50
+ *
51
+ * @throws {MediaTypeParserError} when parsing of Accept header fails
52
+ */
53
+ export declare const getAcceptableMediaTypes: (header: string | undefined) => JsonApiMediaType[];
@@ -0,0 +1,114 @@
1
+ import { MediaTypeParser, MediaTypeParserError } from "./media-type-parser.js";
2
+ /**
3
+ * Parses and represents an HTTP `Accept` header as structured data
4
+ */
5
+ export class AcceptParser extends MediaTypeParser {
6
+ static default = {
7
+ type: "*",
8
+ subType: "*",
9
+ parameters: {},
10
+ weight: 1,
11
+ acceptExt: {},
12
+ };
13
+ static weightRegexp = /^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/;
14
+ /**
15
+ * Parses the Accept header string into a structured `ParsedAccept` array
16
+ *
17
+ * @throws {MediaTypeParserError} when parsing of Accept header fails
18
+ */
19
+ static parse(header) {
20
+ const parser = new AcceptParser(header);
21
+ return parser.process();
22
+ }
23
+ /**
24
+ * Parses the header into an ordered list of media types
25
+ */
26
+ process() {
27
+ this.skipWhitespace();
28
+ if (this.index === this.length) {
29
+ return [AcceptParser.default];
30
+ }
31
+ const accept = [];
32
+ let mediaType;
33
+ let hasMore;
34
+ do {
35
+ [mediaType, hasMore] = this.readMediaType();
36
+ accept.push(mediaType);
37
+ } while (hasMore);
38
+ accept.sort((a, b) => b.weight - a.weight);
39
+ return accept;
40
+ }
41
+ readMediaType() {
42
+ const type = this.readToken().toLowerCase();
43
+ this.consumeChar("/");
44
+ const subType = this.readToken().toLowerCase();
45
+ this.skipWhitespace();
46
+ if (this.index === this.length) {
47
+ return [{ ...AcceptParser.default, type, subType }, false];
48
+ }
49
+ if (this.readSeparator() === ",") {
50
+ this.skipWhitespace();
51
+ return [{ ...AcceptParser.default, type, subType }, true];
52
+ }
53
+ const parameters = {};
54
+ let weight = 1;
55
+ const acceptExt = {};
56
+ let parameterTarget = parameters;
57
+ for (const [name, value] of this.readParameters(true)) {
58
+ if (name === "q") {
59
+ parameterTarget = acceptExt;
60
+ if (!AcceptParser.weightRegexp.test(value)) {
61
+ throw new MediaTypeParserError(`Invalid weight: ${value}`);
62
+ }
63
+ weight = Number.parseFloat(value);
64
+ continue;
65
+ }
66
+ parameterTarget[name] = value;
67
+ }
68
+ this.skipWhitespace();
69
+ const hasMore = this.index < this.length;
70
+ if (hasMore) {
71
+ this.consumeChar(",");
72
+ this.skipWhitespace();
73
+ }
74
+ return [{ type, subType, parameters, weight, acceptExt }, hasMore];
75
+ }
76
+ readSeparator() {
77
+ // No need for an index check here, as the caller already took care of it.
78
+ const char = this.header[this.index];
79
+ this.index += 1;
80
+ if (char !== "," && char !== ";") {
81
+ throw new MediaTypeParserError(`Unexpected character at pos ${this.index - 1}, expected separator`);
82
+ }
83
+ return char;
84
+ }
85
+ }
86
+ /**
87
+ * Filters and extracts valid JSON:API media types from an `Accept` header
88
+ *
89
+ * Only includes entries with:
90
+ *
91
+ * - `type` of `application` or `*`
92
+ * - `subType` of `vnd.api+json` or `*`
93
+ * - No extra parameters beyond `ext` and `profile`
94
+ *
95
+ * @throws {MediaTypeParserError} when parsing of Accept header fails
96
+ */
97
+ export const getAcceptableMediaTypes = (header) => {
98
+ const accept = AcceptParser.parse(header ?? "");
99
+ return accept.reduce((accept, mediaType) => {
100
+ if ((mediaType.type !== "*" && mediaType.type !== "application") ||
101
+ (mediaType.subType !== "*" && mediaType.subType !== "vnd.api+json")) {
102
+ return accept;
103
+ }
104
+ const { ext, profile, ...rest } = mediaType.parameters;
105
+ if (Object.keys(rest).length !== 0) {
106
+ return accept;
107
+ }
108
+ accept.push({
109
+ ext: ext ? ext.split(" ") : [],
110
+ profile: profile ? profile.split(" ") : [],
111
+ });
112
+ return accept;
113
+ }, []);
114
+ };
@@ -0,0 +1,18 @@
1
+ import { MediaTypeParser } from "./media-type-parser.js";
2
+ export type ParsedContentType = {
3
+ type: string;
4
+ subType: string;
5
+ parameters: Record<string, string>;
6
+ };
7
+ /**
8
+ * Parses and represents an HTTP `Content-Type` header as structured data
9
+ */
10
+ export declare class ContentTypeParser extends MediaTypeParser {
11
+ /**
12
+ * Parses the Content-Type header string into a structured `ParsedContentType` object
13
+ *
14
+ * @throws {ParserError} when parsing of Content-Type header fails
15
+ */
16
+ static parse(header: string): ParsedContentType;
17
+ private process;
18
+ }
@@ -0,0 +1,36 @@
1
+ import { MediaTypeParser, MediaTypeParserError } from "./media-type-parser.js";
2
+ /**
3
+ * Parses and represents an HTTP `Content-Type` header as structured data
4
+ */
5
+ export class ContentTypeParser extends MediaTypeParser {
6
+ /**
7
+ * Parses the Content-Type header string into a structured `ParsedContentType` object
8
+ *
9
+ * @throws {ParserError} when parsing of Content-Type header fails
10
+ */
11
+ static parse(header) {
12
+ const parser = new ContentTypeParser(header);
13
+ return parser.process();
14
+ }
15
+ process() {
16
+ this.skipWhitespace();
17
+ const type = this.readToken().toLowerCase();
18
+ this.consumeChar("/");
19
+ const subType = this.readToken().toLowerCase();
20
+ const parameters = {};
21
+ this.skipWhitespace();
22
+ if (this.index === this.length) {
23
+ return { type, subType, parameters };
24
+ }
25
+ if (this.header[this.index] !== ";") {
26
+ throw new MediaTypeParserError(`Unexpected character at pos ${this.index}, expected separator`);
27
+ }
28
+ this.index += 1;
29
+ if (this.index < this.length) {
30
+ for (const [name, value] of this.readParameters(false)) {
31
+ parameters[name] = value;
32
+ }
33
+ }
34
+ return { type, subType, parameters };
35
+ }
36
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./accept.js";
2
+ export * from "./content-type.js";
3
+ export { MediaTypeParserError } from "./media-type-parser.js";
4
+ export * from "./search-params.js";