Skip to content

Commit 7e8e648

Browse files
committed
Test autogenering av typer fra openapi
1 parent 9d6a86a commit 7e8e648

9 files changed

+2246
-9
lines changed

.prettierignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.nais/*
22
.github/*
33
.husky/*
4-
graphql/*
4+
graphql/*
5+
openapi/*

app/models/oppgave.server.ts

+23-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import createClient from "openapi-fetch";
2+
13
import { getSaksbehandlingOboToken } from "~/utils/auth.utils.server";
24
import { getEnv } from "~/utils/env.utils";
35
import { handleErrorResponse } from "~/utils/error-response.server";
46
import { getHeaders } from "~/utils/fetch.utils";
7+
import { parseSearchParamsToOpenApiQuery } from "~/utils/type-guards";
8+
9+
import { paths } from "../../openapi/saksbehandling-typer";
510

611
export interface IPerson {
712
ident: string;
@@ -108,23 +113,34 @@ export type IOppgaveTilstand =
108113
| "FERDIG_BEHANDLET"
109114
| "BEHANDLES_I_ARENA";
110115

116+
const saksbehandlerClient = createClient<paths>({ baseUrl: getEnv("DP_SAKSBEHANDLING_URL") });
117+
111118
export async function hentOppgaver(
112119
request: Request,
113-
urlParams?: string,
114-
): Promise<IOppgaveListeResponse> {
120+
urlSearchParams: URLSearchParams,
121+
): Promise<paths["/oppgave"]["get"]["responses"]["200"]["content"]["application/json"]> {
115122
const onBehalfOfToken = await getSaksbehandlingOboToken(request);
116-
const url = `${getEnv("DP_SAKSBEHANDLING_URL")}/oppgave${urlParams || ""}`;
123+
const queryParams =
124+
parseSearchParamsToOpenApiQuery<paths["/oppgave"]["get"]["parameters"]["query"]>(
125+
urlSearchParams,
126+
);
117127

118-
const response = await fetch(url, {
119-
method: "GET",
128+
const { response, data, error } = await saksbehandlerClient.GET("/oppgave", {
120129
headers: getHeaders(onBehalfOfToken),
130+
params: {
131+
query: queryParams,
132+
},
121133
});
122134

123-
if (!response.ok) {
135+
if (data) {
136+
return data;
137+
}
138+
139+
if (!response.ok || error) {
124140
handleErrorResponse(response);
125141
}
126142

127-
return await response.json();
143+
throw new Error("Uhåndtert feil i hentOppgaver()");
128144
}
129145

130146
export async function hentOppgave(request: Request, oppgaveId: string): Promise<IOppgave> {

app/routes/_index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
4747
}
4848
}
4949

50-
const oppgaverResponse = await hentOppgaver(request, url.search);
50+
const oppgaverResponse = await hentOppgaver(request, url.searchParams);
5151
const statistikk = await hentStatistikkForSaksbehandler(request);
5252
const session = await getSession(request.headers.get("Cookie"));
5353
const alert = session.get("alert");

app/utils/type-guards.ts

+101
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,104 @@ export function isIMeldingOmVedtak(data: unknown): data is IMeldingOmVedtak {
9292

9393
return hasValidHtml && hasValidUtvidedeBeskrivelser;
9494
}
95+
96+
/**
97+
* Represents a primitive value type that can be parsed from a URL parameter
98+
*/
99+
type PrimitiveValue = string | number | boolean;
100+
101+
/**
102+
* Represents possible parsed values from URL parameters
103+
*/
104+
type ParsedValue = PrimitiveValue | PrimitiveValue[];
105+
106+
/**
107+
* Converts URLSearchParams to a typed query object based on OpenAPI path schema
108+
* @param searchParams - The URLSearchParams object or URLSearchParams-like object to parse
109+
* @returns A typed query object with correctly parsed values
110+
*/
111+
export function parseSearchParamsToOpenApiQuery<T extends Record<string, unknown> | undefined>(
112+
searchParams: URLSearchParams | Record<string, string> | string,
113+
): T {
114+
// Ensure we're working with a proper URLSearchParams object
115+
const params = ensureURLSearchParams(searchParams);
116+
const result: Record<string, ParsedValue> = {};
117+
118+
// Use forEach which is more reliably available than entries()
119+
params.forEach((value, key) => {
120+
// Skip empty values
121+
if (value === "") return;
122+
123+
// Check if the key already exists in the result (for array parameters)
124+
if (key in result) {
125+
// If the existing value is already an array, push to it
126+
if (Array.isArray(result[key])) {
127+
(result[key] as PrimitiveValue[]).push(parseValue(value));
128+
} else {
129+
// Convert existing value to an array and add the new value
130+
result[key] = [result[key] as PrimitiveValue, parseValue(value)];
131+
}
132+
} else {
133+
// Get all values for this key to check if it should be an array
134+
const allValues = params.getAll(key);
135+
136+
if (allValues.length > 1) {
137+
// If multiple values exist, create an array
138+
result[key] = allValues.map(parseValue);
139+
} else {
140+
// Otherwise, set as a single value
141+
result[key] = parseValue(value);
142+
}
143+
}
144+
});
145+
146+
return result as T;
147+
}
148+
149+
/**
150+
* Helper function to ensure we're working with a URLSearchParams object
151+
* @param input - The input to convert to URLSearchParams
152+
* @returns A URLSearchParams object
153+
*/
154+
function ensureURLSearchParams(
155+
input: URLSearchParams | Record<string, string> | string,
156+
): URLSearchParams {
157+
if (input instanceof URLSearchParams) {
158+
return input;
159+
}
160+
161+
if (typeof input === "string") {
162+
return new URLSearchParams(input);
163+
}
164+
165+
// Handle plain objects by creating a new URLSearchParams
166+
if (typeof input === "object") {
167+
const params = new URLSearchParams();
168+
Object.entries(input).forEach(([key, value]) => {
169+
params.append(key, value);
170+
});
171+
return params;
172+
}
173+
174+
// Default fallback
175+
return new URLSearchParams();
176+
}
177+
178+
/**
179+
* Helper function to parse string values to appropriate types
180+
* @param value - The string value to parse
181+
* @returns The parsed value with the correct type
182+
*/
183+
function parseValue(value: string): PrimitiveValue {
184+
// Parse boolean values
185+
if (value.toLowerCase() === "true") return true;
186+
if (value.toLowerCase() === "false") return false;
187+
188+
// Parse numeric values
189+
if (!isNaN(Number(value)) && value.trim() !== "") {
190+
return Number(value);
191+
}
192+
193+
// Keep strings as is
194+
return value;
195+
}

0 commit comments

Comments
 (0)