Skip to content

Commit

Permalink
feat(types): add Range API
Browse files Browse the repository at this point in the history
  • Loading branch information
TomokiMiyauci committed Mar 10, 2023
1 parent c66c301 commit 50d9eab
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 43 deletions.
58 changes: 27 additions & 31 deletions transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
import {
ConditionalHeader,
isErr,
isIntRange,
isNotEmpty,
isNull,
isOtherRange,
Method,
parse,
RangeHeader,
type RangeSpec,
RangesSpecifier,
RepresentationHeader,
Status,
unsafe,
Expand All @@ -19,9 +18,9 @@ import {
RangeUnit as Unit,
RequestedRangeNotSatisfiableResponse,
shallowMergeHeaders,
toSpecifier,
} from "./util.ts";

import type { Range, RangeUnit, Specifier } from "./types.ts";
import type { Range, RangeUnit } from "./types.ts";

export function withAcceptRanges(
response: Response,
Expand Down Expand Up @@ -68,35 +67,31 @@ export async function withContentRange(
}

const parsedRange = rangeContainer.value;
const maybeRange = Array.from(context.ranges).find(matchRange);
const matchedRange = matchRange(parsedRange, context.ranges);
const body = await response.clone().arrayBuffer();

if (
!maybeRange ||
!parsedRange
.rangeSet
.map(toSpecifier)
.every((specifier) => maybeRange.specifiers.includes(specifier))
) {
if (!matchedRange) {
// @see https://www.rfc-editor.org/rfc/rfc9110#section-14.2-13
return new RequestedRangeNotSatisfiableResponse({
rangeUnit: parsedRange.rangeUnit,
completeLength: body.byteLength,
}, { headers: response.headers });
}

const matchedRange = maybeRange;
const satisfiableRangeSet = parsedRange.rangeSet.filter(isSatisfiable);
const targetRangeSet = matchedRange.getSatisfiable({
content: body,
rangeSet: parsedRange.rangeSet,
});

if (!satisfiableRangeSet.length) {
if (!isNotEmpty(targetRangeSet)) {
return new RequestedRangeNotSatisfiableResponse({
rangeUnit: matchedRange.unit,
completeLength: body.byteLength,
}, { headers: response.headers });
}

const partialContents = await matchedRange.partial({
rangeSet: parsedRange.rangeSet,
const partialContents = await matchedRange.getPartial({
rangeSet: targetRangeSet,
content: body,
contentType,
});
Expand All @@ -109,21 +104,22 @@ export async function withContentRange(
status: Status.PartialContent,
headers,
});
}

function isSatisfiable(rangeSpec: RangeSpec): boolean {
return matchedRange.isSatisfiable({ contents: body, rangeSpec });
}
function matchRange(
range: RangesSpecifier,
ranges: Iterable<Range>,
): null | Range {
const maybeRange = Array.from(ranges).find(({ unit }) =>
unit === range.rangeUnit
);

function matchRange(range: Range): boolean {
return range.unit === parsedRange.rangeUnit;
}
}
if (!maybeRange) return null;

function toSpecifier(
rangeSpec: RangeSpec,
): Specifier {
if (isOtherRange(rangeSpec)) return "other-range";
if (isIntRange(rangeSpec)) return "int-range";
const result = range
.rangeSet
.map(toSpecifier)
.every((specifier) => maybeRange.specifiers.includes(specifier));

return "suffix-range";
return result ? maybeRange : null;
}
25 changes: 13 additions & 12 deletions transform_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Status,
} from "./_dev_deps.ts";
import type { RangeUnit } from "./types.ts";
import { Specifier } from "./util.ts";

describe("withContentRange", () => {
describe("should return same response", () => {
Expand Down Expand Up @@ -150,9 +151,9 @@ describe("withContentRange", () => {
{
ranges: [{
unit: "xxx",
specifiers: ["int-range"],
isSatisfiable: () => true,
partial: () => ({ content: "", headers: new Headers() }),
specifiers: [Specifier.IntRange],
getSatisfiable: () => [],
getPartial: () => ({ content: "", headers: new Headers() }),
}],
},
);
Expand All @@ -177,9 +178,9 @@ describe("withContentRange", () => {
{
ranges: [{
unit: "bytes",
specifiers: ["other-range"],
isSatisfiable: () => true,
partial: () => ({ content: "", headers: new Headers() }),
specifiers: [Specifier.OtherRange],
getSatisfiable: () => [],
getPartial: () => ({ content: "", headers: new Headers() }),
}],
},
);
Expand All @@ -205,9 +206,9 @@ describe("withContentRange", () => {
{
ranges: [{
unit: "bytes",
specifiers: ["int-range"],
isSatisfiable: () => false,
partial: () => ({ content: "", headers: new Headers() }),
specifiers: [Specifier.IntRange],
getSatisfiable: () => [],
getPartial: () => ({ content: "", headers: new Headers() }),
}],
},
);
Expand Down Expand Up @@ -235,9 +236,9 @@ describe("withContentRange", () => {
{
ranges: [{
unit: "bytes",
specifiers: ["int-range"],
isSatisfiable: () => true,
partial: () => ({
specifiers: [Specifier.IntRange],
getSatisfiable: () => [{ firstPos: 0, lastPos: undefined }],
getPartial: () => ({
content: "efgh",
headers: new Headers({ [RangeHeader.ContentRange]: "range" }),
}),
Expand Down
41 changes: 41 additions & 0 deletions types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { IntRange, OtherRange, type RangeSpec, SuffixRange } from "./deps.ts";
import { Specifier } from "./util.ts";

interface SpecifierMap {
[Specifier.IntRange]: IntRange;
[Specifier.SuffixRange]: SuffixRange;
[Specifier.OtherRange]: OtherRange;
}

export interface Range<in out S extends Specifier = Specifier> {
readonly unit: string;
readonly specifiers: readonly S[];
readonly getSatisfiable: GetSatisfiableCallback<SpecifierMap[S]>;
readonly getPartial: GetPartialCallback<SpecifierMap[S]>;
}

export interface PartialContext<R extends RangeSpec = RangeSpec> {
readonly rangeSet: readonly [R, ...readonly R[]];
readonly content: ArrayBuffer;
readonly contentType: string;
}

export interface IsSatisfiableContext<R extends RangeSpec = RangeSpec> {
readonly rangeSet: readonly R[];
readonly content: ArrayBuffer;
}

export interface GetSatisfiableCallback<R extends RangeSpec = RangeSpec> {
(context: IsSatisfiableContext<R>): R[];
}

export interface PartialContent {
readonly content: BodyInit;
readonly headers: Headers;
}

export interface GetPartialCallback<R extends RangeSpec = RangeSpec> {
(context: PartialContext<R>): PartialContent | Promise<PartialContent>;
}

export type RangeUnit = "bytes" | "none";

0 comments on commit 50d9eab

Please sign in to comment.