UNPKG

@oada/well-known-json

Version:

Middleware for JSON Well-Known resources

129 lines (110 loc) 3.36 kB
/** * @license * Copyright 2014-2022 Open Ag Data Alliance * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const RELATIVE = /^\.\.?\//; export interface Options { baseUri?: string; } type Collection<T> = Record<string, T> | Iterable<[string, T]>; type Resources = Collection<Collection<unknown>>; function isIterable<T>(value: unknown): value is Iterable<T> { return typeof value === 'object' && Symbol.iterator in (value ?? {}); } interface Entries<K, V> { entries(): Iterable<[K, V]>; } function hasEntries<K, V>(value: unknown): value is Entries<K, V> { return ( typeof value === 'object' && // @ts-expect-error stuff typeof value?.entries === 'function' ); } function normalize<T>( collection: Collection<Collection<T>> ): Map<string, Map<string, T>> { const entries = isIterable(collection) ? collection : Object.entries(collection); return new Map( Array.from(entries, ([k, v]) => [ k, new Map(isIterable(v) ? v : Object.entries(v)), ]) ); } export class WellKnownJSON<In = unknown, Out = unknown> { // eslint-disable-next-line @typescript-eslint/naming-convention static readonly METHODS = ['OPTIONS', 'GET', 'HEAD'] as const; #base; #json; constructor(resources: Resources, options: Options = {}) { this.#base = options.baseUri; this.#json = normalize(resources); } /** * Get a particular well-known resource * * @param name * @param base * @returns */ getResource(name: string, baseIn?: string, ...rest: [In, Out]) { const base = this.#base ?? baseIn; const match = this.#json.get(name); return resourceify(match); function resourceify(value: unknown): unknown { if (!value) { return value; } switch (typeof value) { case 'string': { return RELATIVE.test(value) ? new URL(value, base).toString() : value; } case 'object': { if (!value) { return value; } if (Array.isArray(value)) { return value.map((element) => resourceify(element)); } const entries = hasEntries(value) ? value.entries() : Object.entries(value); return Object.fromEntries( Array.from(entries, ([k, v]) => [k, resourceify(v)]) ); } case 'function': { /* Call functions */ return resourceify(value(...rest)); } default: { return value; } } } } /** * Add Well-Known resource, merging with any preexisting ones */ addResource(uri: string, collection: Collection<unknown>) { const entries = isIterable(collection) ? collection : Object.entries(collection); this.#json.set(uri, new Map([...(this.#json.get(uri) ?? []), ...entries])); } }