diff --git a/src/entities/project-entities.ts b/src/entities/project-entities.ts index d56516d..c3cbbf5 100644 --- a/src/entities/project-entities.ts +++ b/src/entities/project-entities.ts @@ -1,3 +1,4 @@ +/** Each project is like a "folder" to store multiple specs of requests. */ export interface Project { readonly uuid: string; readonly name: string; @@ -5,11 +6,13 @@ export interface Project { readonly specs: ProjectRequestSpec[]; } +/** TODO: to organize requests as "subfolders" in a project. */ export interface ProjectSection { readonly uuid: string; readonly name: string; } +/** User specification of how its request looks like. */ export interface ProjectRequestSpec { readonly uuid: string; readonly url: string; @@ -18,6 +21,7 @@ export interface ProjectRequestSpec { readonly body: string; } +/** Data structure of HTTP headers specified in a `ProjectRequestSpec`, can be toggled. */ export interface ProjectRequestSpecHeader { readonly key: string; readonly value: string; diff --git a/src/entities/runtime-entities.ts b/src/entities/runtime-entities.ts index 28526d8..2ca6d9f 100644 --- a/src/entities/runtime-entities.ts +++ b/src/entities/runtime-entities.ts @@ -1,20 +1,46 @@ +/** + * Representation of each spec being put to exectution, + * become concrete and not only a spec. + */ export interface RuntimeState { + /** + * `"idle"` is waiting runtime didn't start yet. + * `"running"` is request being performed right now. + * `"success"` is request finished, responded with ok status. + * `"unsuccess"` is request finished, responded with not ok status. + * `"error"` for when the request didn't even reach the server. + */ readonly step: "idle" | "running" | "success" | "unsuccess" | "error"; - readonly text: string; - readonly status: number; + /** Observability snapshot of the request. With blank values while `step="idle"`. */ + readonly request: RequestInfo; + /** Observability of the response. With blank values if there wasn't a server response yet. */ + readonly response: ResponseInfo; + /** Any exception unrelated to the direct 1:1 request-response relation (see `step="error"`). */ + readonly errorMessage: string; + /** Useful metadata for performance visibility. */ readonly startedAt: number; + /** Useful metadata for performance visibility. */ readonly finishedAt: number; } +/** + * Applied final data used to make a real request run. + * + * However, due to browser policies, the actual request headers for example are not + * fully acessible. There's no API available to preview what the browser itself intercepted. + */ export interface RequestInfo { readonly url: string; readonly method: string; readonly body: string; - readonly headers: { [key: string]: string }; + readonly headers: Array<{ key: string; value: string }>; } +/** + * Response received by the server after a request. + */ export interface ResponseInfo { readonly status: number; readonly body: string; - readonly headers: { [key: string]: string }; + readonly headers: Array<{ key: string; value: string }>; } diff --git a/src/features/project-workspace/ProjectWorkspacePage.tsx b/src/features/project-workspace/ProjectWorkspacePage.tsx index 90436fe..c109fcd 100644 --- a/src/features/project-workspace/ProjectWorkspacePage.tsx +++ b/src/features/project-workspace/ProjectWorkspacePage.tsx @@ -24,6 +24,11 @@ import { } from "@/components/ui/dialog"; import { RequestsSpecsContextProvider } from "./RequestsSpecsContextProvider"; import { useRequestsSpecs } from "./RequestsSpecsContext"; +import { + RuntimeState, + RequestInfo, + ResponseInfo, +} from "@/entities/runtime-entities"; import { Input } from "@/components/ui/input"; import { Select, @@ -33,7 +38,6 @@ import { SelectValue, } from "@/components/ui/select"; import { Progress } from "@/components/ui/progress"; -import { RuntimeState } from "@/entities/runtime-entities"; export function ProjectWorkspacePage() { const params = useParams(); @@ -190,36 +194,49 @@ function RequestSpecEditor(props: { const [runtime, setRuntime] = useState({ step: "idle", - text: "", - status: 0, + request: { + url: "", + method: "", + body: "", + headers: [], + }, + response: { + status: 0, + body: "", + headers: [], + }, + errorMessage: "", startedAt: 0, finishedAt: 0, }); - const begin = () => + const begin = (request: RequestInfo) => setRuntime({ step: "running", - text: "", - status: 0, + request, + response: { + status: 0, + body: "", + headers: [], + }, + errorMessage: "", startedAt: Date.now(), finishedAt: 0, }); - const success = (text: string, status: number) => + const success = (response: ResponseInfo) => setRuntime((updatingRuntime) => ({ ...updatingRuntime, step: "success", - status, - text, + response, finishedAt: Date.now(), })); - const unsuccess = (text: string, status: number) => + const unsuccess = (response: ResponseInfo) => setRuntime((updatingRuntime) => ({ ...updatingRuntime, step: "unsuccess", - text, - status, + response, finishedAt: Date.now(), })); @@ -232,16 +249,35 @@ function RequestSpecEditor(props: { })); const runSpec = async (running: { method: string; url: string }) => { - begin(); + const requestInfo: RequestInfo = { + url: running.url, + method: running.method, + body: "", + headers: spec?.headers.length + ? spec.headers.filter((h) => h.isEnabled) + : [], + }; + + begin(requestInfo); try { const response = await fetch(running.url, { - method: running.method, + method: requestInfo.method, + headers: requestInfo.headers.reduce( + (headers, header) => ({ ...headers, [header.key]: header.value }), + {} + ), }); - const text = await response.text(); + const responseInfo: ResponseInfo = { + status: response.status, + body: await response.text(), + headers: Object.entries(response.headers).map( + ([key, value]: [string, string]) => ({ key, value }) + ), + }; if (response.ok) { - success(text, response.status); + success(responseInfo); } else { - unsuccess(text, response.status); + unsuccess(responseInfo); } } catch (exception) { error((exception as Error).message); @@ -308,11 +344,12 @@ function RequestSpecEditor(props: { {runtime.step === "success" && ( <>

- HTTP success: {runtime.status} + HTTP success:{" "} + {runtime.response.status}

- {runtime.text ? ( + {runtime.response.body ? (

- {runtime.text} + {runtime.response.body}

) : (

@@ -324,11 +361,12 @@ function RequestSpecEditor(props: { {runtime.step === "unsuccess" && ( <>

- HTTP bad code: {runtime.status} + HTTP bad code:{" "} + {runtime.response.status}

- {runtime.text ? ( + {runtime.response.body ? (

- {runtime.text} + {runtime.response.body}

) : (

@@ -340,9 +378,10 @@ function RequestSpecEditor(props: { {runtime.step === "error" && ( <>

Error.

- {runtime.text ? ( + {runtime.errorMessage ? (

- Browser reason: {runtime.text} + Browser reason:{" "} + {runtime.errorMessage}

) : (