diff --git a/helpers/types/HoppRESTRequest.ts b/helpers/types/HoppRESTRequest.ts new file mode 100644 index 000000000..02355ee5e --- /dev/null +++ b/helpers/types/HoppRESTRequest.ts @@ -0,0 +1,10 @@ +export type HoppRESTParam = { + key: string + value: string + active: boolean +} + +export interface HoppRESTRequest { + endpoint: string + params: HoppRESTParam[] +} diff --git a/helpers/utils/EffectiveURL.ts b/helpers/utils/EffectiveURL.ts new file mode 100644 index 000000000..9ef9a6050 --- /dev/null +++ b/helpers/utils/EffectiveURL.ts @@ -0,0 +1,37 @@ +import { combineLatest, Observable } from "rxjs" +import { map } from "rxjs/operators" +import { HoppRESTRequest } from "../types/HoppRESTRequest" +import { Environment } from "~/newstore/environments" + +interface EffectiveHoppRESTRequest extends HoppRESTRequest { + /** + * The effective final URL. + * + * This contains path, params and environment variables all applied to it + */ + effectiveFinalURL: string +} + +/** + * Creates an Observable Stream that emits HoppRESTRequests whenever + * the input streams emit a value + * + * @param request$ The request stream containing request data + * @param environment$ The environment stream containing environment data to apply + * + * @returns Observable Stream for the Effective Request Object + */ +export function getEffectiveRESTRequestStream( + request$: Observable, + environment$: Observable +): Observable { + return combineLatest([request$, environment$]).pipe( + map(([request, _env]) => { + // TODO: Change this + return { + ...request, + effectiveFinalURL: request.endpoint, + } + }) + ) +} diff --git a/helpers/utils/StreamUtils.ts b/helpers/utils/StreamUtils.ts new file mode 100644 index 000000000..fad17f58e --- /dev/null +++ b/helpers/utils/StreamUtils.ts @@ -0,0 +1,24 @@ +import { combineLatest, Observable } from "rxjs" +import { map } from "rxjs/operators" + +/** + * Constructs a stream of a object from a collection of other observables + * + * @param streamObj The object containing key of observables to assemble from + * + * @returns The constructed object observable + */ +export function constructFromStreams( + streamObj: { [key in keyof T]: Observable } +): Observable { + return combineLatest(Object.values>(streamObj)).pipe( + map((streams) => { + const keys = Object.keys(streamObj) as (keyof T)[] + + return keys.reduce( + (acc, s, i) => Object.assign(acc, { [s]: streams[i] }), + {} + ) as T + }) + ) +} diff --git a/newstore/RESTSession.ts b/newstore/RESTSession.ts new file mode 100644 index 000000000..b9e85b6e5 --- /dev/null +++ b/newstore/RESTSession.ts @@ -0,0 +1,121 @@ +import { pluck, distinctUntilChanged } from "rxjs/operators" +import DispatchingStore, { defineDispatchers } from "./DispatchingStore" +import { HoppRESTParam, HoppRESTRequest } from "~/helpers/types/HoppRESTRequest" + +function getParamsInURL(url: string): { key: string; value: string }[] { + const result: { key: string; value: string }[] = [] + + try { + const uriObj = new URL(url) + + uriObj.searchParams.forEach((value, key) => { + result.push({ key, value }) + }) + } catch (_e) {} + + return result +} + +function recalculateParams( + oldURL: string, + currentParams: HoppRESTParam[], + newParams: { key: string; value: string }[] +): HoppRESTParam[] { + const paramsInOldURL = getParamsInURL(oldURL).map((x) => x.key) + + const checkingParams = currentParams.filter( + (x) => !paramsInOldURL.includes(x.key) + ) + + const result: HoppRESTParam[] = [] + + const addedKeys: string[] = [] + + newParams.forEach(({ key, value }) => { + const currentParam = checkingParams.find( + ({ key: currentKey }) => currentKey === key + ) + + if (!currentParam) { + addedKeys.push(key) + result.push({ key, value, active: true }) + } + }) + + result.push(...checkingParams.filter((x) => !addedKeys.includes(x.key))) + + return result +} + +type RESTSession = { + request: HoppRESTRequest +} + +const defaultRESTSession: RESTSession = { + request: { + endpoint: "https://httpbin.org/", + params: [], + }, +} + +const dispatchers = defineDispatchers({ + setEndpoint(curr: RESTSession, { newEndpoint }: { newEndpoint: string }) { + const paramsInNewURL = getParamsInURL(newEndpoint) + const updatedParams = recalculateParams( + curr.request.endpoint, + curr.request.params, + paramsInNewURL + ) + + return { + request: { + ...curr.request, + endpoint: newEndpoint, + params: updatedParams, + }, + } + }, + addParam(curr: RESTSession, { newParam }: { newParam: HoppRESTParam }) { + return { + request: { + ...curr.request, + params: [...curr.request.params, newParam], + }, + } + }, +}) + +const restSessionStore = new DispatchingStore(defaultRESTSession, dispatchers) + +export function setRESTEndpoint(newEndpoint: string) { + restSessionStore.dispatch({ + dispatcher: "setEndpoint", + payload: { + newEndpoint, + }, + }) +} + +export function addRESTParam(newParam: HoppRESTParam) { + restSessionStore.dispatch({ + dispatcher: "addParam", + payload: { + newParam, + }, + }) +} + +export const restRequest$ = restSessionStore.subject$.pipe( + pluck("request"), + distinctUntilChanged() +) + +export const restEndpoint$ = restSessionStore.subject$.pipe( + pluck("request", "endpoint"), + distinctUntilChanged() +) + +export const restParams$ = restSessionStore.subject$.pipe( + pluck("request", "params"), + distinctUntilChanged() +) diff --git a/pages/index.vue b/pages/index.vue index eb3518cc1..54d3a0b3c 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -72,7 +72,7 @@ id="url" name="url" type="text" - v-model="uri" + v-model="newEndpoint$" spellcheck="false" @input="pathInputHandler" :placeholder="$t('url')" @@ -254,13 +254,13 @@ :label=" $t('parameters') + `${ - params.length !== 0 ? ' \xA0 • \xA0 ' + params.length : '' + newParams$.length !== 0 ? ' \xA0 • \xA0 ' + newParams$.length : '' }` " :selected="true" > console.log(x)) this._keyListener = function (e) { if (e.key === "g" && (e.ctrlKey || e.metaKey)) { e.preventDefault()