updated react-openapi
This commit is contained in:
21
react-openapi/src/context/AppContext.tsx
Normal file
21
react-openapi/src/context/AppContext.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { createContext, useContext } from "react";
|
||||
import type { ResourceConfig, SpecConfiguration, ValidationMessage } from "../types";
|
||||
|
||||
export interface AppContextValue {
|
||||
config: SpecConfiguration;
|
||||
resources: ResourceConfig[];
|
||||
schemas: Record<string, any>;
|
||||
loading: boolean;
|
||||
errors: ValidationMessage[];
|
||||
warnings: ValidationMessage[];
|
||||
}
|
||||
|
||||
export const AppContext = createContext<AppContextValue | null>(null);
|
||||
|
||||
export function useAppContext(): AppContextValue {
|
||||
const ctx = useContext(AppContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useAppContext must be used within an AppProvider");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
83
react-openapi/src/context/AppProvider.tsx
Normal file
83
react-openapi/src/context/AppProvider.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import React, { useEffect, useState, useMemo } from "react";
|
||||
import type { SpecConfiguration, ResourceConfig, ValidationMessage } from "../types";
|
||||
import { AppContext } from "./AppContext";
|
||||
import { loadSpec } from "../spec-loader";
|
||||
import { validateSpec } from "../spec-validator";
|
||||
import { buildResourceConfigs } from "../transformers/resource-config";
|
||||
import { initApi } from "../hooks/useApi";
|
||||
|
||||
interface AppProviderProps {
|
||||
specConfiguration: SpecConfiguration;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function AppProvider({ specConfiguration, children }: AppProviderProps) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [resources, setResources] = useState<ResourceConfig[]>([]);
|
||||
const [schemas, setSchemas] = useState<Record<string, any>>({});
|
||||
const [errors, setErrors] = useState<ValidationMessage[]>([]);
|
||||
const [warnings, setWarnings] = useState<ValidationMessage[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const spec = await loadSpec(specConfiguration.specUrl);
|
||||
|
||||
const allMessages = validateSpec(spec);
|
||||
|
||||
const errs = allMessages.filter((m) => m.type === "error");
|
||||
const warns = allMessages.filter((m) => m.type === "warning");
|
||||
|
||||
if (!cancelled) {
|
||||
setErrors(errs);
|
||||
setWarnings(warns);
|
||||
setSchemas(spec.components?.schemas ?? {});
|
||||
}
|
||||
|
||||
if (errs.length === 0) {
|
||||
const configs = buildResourceConfigs(spec);
|
||||
if (!cancelled) {
|
||||
setResources(configs);
|
||||
}
|
||||
|
||||
const baseUrl = specConfiguration.baseApiUrl ?? spec.servers?.[0]?.url ?? "";
|
||||
if (baseUrl) {
|
||||
initApi(baseUrl, specConfiguration.getToken);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (!cancelled) {
|
||||
setErrors([{ type: "error", message: e.message ?? "Failed to load spec" }]);
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [specConfiguration.specUrl]);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
config: specConfiguration,
|
||||
resources,
|
||||
schemas,
|
||||
loading,
|
||||
errors,
|
||||
warnings,
|
||||
}),
|
||||
[specConfiguration, resources, schemas, loading, errors, warnings]
|
||||
);
|
||||
|
||||
return React.createElement(AppContext.Provider, { value }, children);
|
||||
}
|
||||
147
react-openapi/src/context/useResource.ts
Normal file
147
react-openapi/src/context/useResource.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import type { ResourceConfig, ParsedListResponse } from "../types";
|
||||
import { getApi } from "../hooks/useApi";
|
||||
|
||||
function parseError(e: any): string {
|
||||
if (e.response?.data) {
|
||||
const data = e.response.data;
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((err: any) => err.msg ?? String(err)).join("; ");
|
||||
}
|
||||
if (typeof data.detail === "string") {
|
||||
return data.detail;
|
||||
}
|
||||
}
|
||||
return e.message ?? "An error occurred";
|
||||
}
|
||||
|
||||
interface ResourceState {
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface UseResourceReturn {
|
||||
list: (params?: Record<string, any>) => Promise<ParsedListResponse>;
|
||||
get: (id: string | number) => Promise<any>;
|
||||
create: (data: any) => Promise<any>;
|
||||
update: (id: string | number, data: any) => Promise<any>;
|
||||
remove: (id: string | number) => Promise<void>;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export function useResource(resource: ResourceConfig): UseResourceReturn {
|
||||
const [state, setState] = useState<ResourceState>({ loading: false, error: null });
|
||||
|
||||
const setLoading = useCallback((loading: boolean) => {
|
||||
setState((s) => ({ ...s, loading }));
|
||||
}, []);
|
||||
|
||||
const setError = useCallback((error: string | null) => {
|
||||
setState((s) => ({ ...s, error }));
|
||||
}, []);
|
||||
|
||||
const list = useCallback(
|
||||
async (params?: Record<string, any>): Promise<ParsedListResponse> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const api = getApi();
|
||||
const res = await api.get(resource.path, { params });
|
||||
const data = res.data;
|
||||
|
||||
if (resource.pagination) {
|
||||
if (!data || typeof data !== "object" || !Array.isArray(data.items)) {
|
||||
throw new Error(`Expected paginated response { total, items } from ${resource.path}`);
|
||||
}
|
||||
return { items: data.items, total: data.total ?? data.items.length };
|
||||
}
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error(`Expected array response from ${resource.path}`);
|
||||
}
|
||||
return { items: data };
|
||||
} catch (e: any) {
|
||||
const msg = parseError(e);
|
||||
setError(msg);
|
||||
return { items: [] };
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[resource.path, resource.pagination, setLoading, setError]
|
||||
);
|
||||
|
||||
const get = useCallback(
|
||||
async (id: string | number): Promise<any> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const api = getApi();
|
||||
const res = await api.get(`${resource.path}/${id}`);
|
||||
return res.data;
|
||||
} catch (e: any) {
|
||||
setError(parseError(e));
|
||||
throw e;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[resource.path, setLoading, setError]
|
||||
);
|
||||
|
||||
const create = useCallback(
|
||||
async (data: any): Promise<any> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const api = getApi();
|
||||
const res = await api.post(resource.path, data);
|
||||
return res.data;
|
||||
} catch (e: any) {
|
||||
setError(parseError(e));
|
||||
throw e;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[resource.path, setLoading, setError]
|
||||
);
|
||||
|
||||
const update = useCallback(
|
||||
async (id: string | number, data: any): Promise<any> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const api = getApi();
|
||||
const res = await api.put(`${resource.path}/${id}`, data);
|
||||
return res.data;
|
||||
} catch (e: any) {
|
||||
setError(parseError(e));
|
||||
throw e;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[resource.path, setLoading, setError]
|
||||
);
|
||||
|
||||
const remove = useCallback(
|
||||
async (id: string | number): Promise<void> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const api = getApi();
|
||||
await api.delete(`${resource.path}/${id}`);
|
||||
} catch (e: any) {
|
||||
setError(parseError(e));
|
||||
throw e;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[resource.path, setLoading, setError]
|
||||
);
|
||||
|
||||
return { list, get, create, update, remove, loading: state.loading, error: state.error };
|
||||
}
|
||||
Reference in New Issue
Block a user