F: Add zod validation for backend responses for rsforms

This commit is contained in:
Ivan 2025-02-17 14:40:18 +03:00
parent 440a655395
commit 869de34fc5
26 changed files with 318 additions and 244 deletions

View File

@ -3,8 +3,10 @@
*/
import { toast } from 'react-toastify';
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
import { z, ZodError } from 'zod';
import { buildConstants } from '@/utils/buildConstants';
import { errorMsg } from '@/utils/labels';
import { extractErrorMessage } from '@/utils/utils';
export { AxiosError } from 'axios';
@ -41,23 +43,32 @@ export interface IAxiosRequest<RequestData, ResponseData> {
endpoint: string;
request?: IFrontRequest<RequestData, ResponseData>;
options?: AxiosRequestConfig;
schema?: z.ZodType;
}
export interface IAxiosGetRequest {
endpoint: string;
options?: AxiosRequestConfig;
signal?: AbortSignal;
schema?: z.ZodType;
}
// ================ Transport API calls ================
export function axiosGet<ResponseData>({ endpoint, options }: IAxiosGetRequest) {
export function axiosGet<ResponseData>({ endpoint, options, schema }: IAxiosGetRequest) {
return axiosInstance
.get<ResponseData>(endpoint, options)
.then(response => response.data)
.then(response => {
schema?.parse(response.data);
return response.data;
})
.catch((error: Error | AxiosError) => {
// Note: Ignore cancellation errors
if (error.name !== 'CanceledError') {
// Note: Ignore cancellation errors
toast.error(extractErrorMessage(error));
if (error instanceof ZodError) {
toast.error(errorMsg.invalidResponse);
} else {
toast.error(extractErrorMessage(error));
}
console.error(error);
}
throw error;
@ -67,11 +78,13 @@ export function axiosGet<ResponseData>({ endpoint, options }: IAxiosGetRequest)
export function axiosPost<RequestData, ResponseData = void>({
endpoint,
request,
options
options,
schema
}: IAxiosRequest<RequestData, ResponseData>) {
return axiosInstance
.post<ResponseData>(endpoint, request?.data, options)
.then(response => {
schema?.parse(response.data);
if (request?.successMessage) {
if (typeof request.successMessage === 'string') {
toast.success(request.successMessage);
@ -81,8 +94,12 @@ export function axiosPost<RequestData, ResponseData = void>({
}
return response.data;
})
.catch((error: Error | AxiosError) => {
toast.error(extractErrorMessage(error));
.catch((error: Error | AxiosError | ZodError) => {
if (error instanceof ZodError) {
toast.error(errorMsg.invalidResponse);
} else {
toast.error(extractErrorMessage(error));
}
console.error(error);
throw error;
});
@ -91,11 +108,13 @@ export function axiosPost<RequestData, ResponseData = void>({
export function axiosDelete<RequestData, ResponseData = void>({
endpoint,
request,
options
options,
schema
}: IAxiosRequest<RequestData, ResponseData>) {
return axiosInstance
.delete<ResponseData>(endpoint, options)
.then(response => {
schema?.parse(response.data);
if (request?.successMessage) {
if (typeof request.successMessage === 'string') {
toast.success(request.successMessage);
@ -105,8 +124,12 @@ export function axiosDelete<RequestData, ResponseData = void>({
}
return response.data;
})
.catch((error: Error | AxiosError) => {
toast.error(extractErrorMessage(error));
.catch((error: Error | AxiosError | ZodError) => {
if (error instanceof ZodError) {
toast.error(errorMsg.invalidResponse);
} else {
toast.error(extractErrorMessage(error));
}
console.error(error);
throw error;
});
@ -115,11 +138,13 @@ export function axiosDelete<RequestData, ResponseData = void>({
export function axiosPatch<RequestData, ResponseData = void>({
endpoint,
request,
options
options,
schema
}: IAxiosRequest<RequestData, ResponseData>) {
return axiosInstance
.patch<ResponseData>(endpoint, request?.data, options)
.then(response => {
schema?.parse(response.data);
if (request?.successMessage) {
if (typeof request.successMessage === 'string') {
toast.success(request.successMessage);
@ -129,8 +154,12 @@ export function axiosPatch<RequestData, ResponseData = void>({
}
return response.data;
})
.catch((error: Error | AxiosError) => {
toast.error(extractErrorMessage(error));
.catch((error: Error | AxiosError | ZodError) => {
if (error instanceof ZodError) {
toast.error(errorMsg.invalidResponse);
} else {
toast.error(extractErrorMessage(error));
}
console.error(error);
throw error;
});

View File

@ -1,11 +1,12 @@
import clsx from 'clsx';
import { ZodError } from 'zod';
import { AxiosError, isAxiosError } from '@/backend/apiTransport';
import { isResponseHtml } from '@/utils/utils';
import { PrettyJson } from './View';
export type ErrorData = string | Error | AxiosError | undefined | null;
export type ErrorData = string | Error | AxiosError | ZodError | undefined | null;
interface InfoErrorProps {
error: ErrorData;
@ -16,6 +17,13 @@ function DescribeError({ error }: { error: ErrorData }) {
return <p>Ошибки отсутствуют</p>;
} else if (typeof error === 'string') {
return <p>{error}</p>;
} else if (error instanceof ZodError) {
return (
<div className='mt-6'>
<p>Ошибка валидации данных</p>
<PrettyJson data={JSON.parse(error.toString()) as unknown} />;
</div>
);
} else if (!isAxiosError(error)) {
return (
<div className='mt-6'>

View File

@ -138,11 +138,11 @@ export const libraryApi = {
successMessage: infoMsg.versionRestored
}
}),
versionUpdate: (data: IVersionUpdateDTO) =>
versionUpdate: (data: { itemID: number; version: IVersionUpdateDTO }) =>
axiosPatch<IVersionUpdateDTO, IVersionInfo>({
endpoint: `/api/versions/${data.id}`,
endpoint: `/api/versions/${data.version.id}`,
request: {
data: data,
data: data.version,
successMessage: infoMsg.changesSaved
}
}),

View File

@ -12,8 +12,8 @@ export const useVersionUpdate = () => {
const mutation = useMutation({
mutationKey: [libraryApi.baseKey, 'update-version'],
mutationFn: libraryApi.versionUpdate,
onSuccess: data => {
client.setQueryData(KEYS.composite.rsItem({ itemID: data.item }), (prev: IRSFormDTO | undefined) =>
onSuccess: (data, variables) => {
client.setQueryData(KEYS.composite.rsItem({ itemID: variables.itemID }), (prev: IRSFormDTO | undefined) =>
!prev
? undefined
: {
@ -26,6 +26,6 @@ export const useVersionUpdate = () => {
}
});
return {
versionUpdate: (data: IVersionUpdateDTO) => mutation.mutateAsync(data)
versionUpdate: (data: { itemID: number; version: IVersionUpdateDTO }) => mutation.mutateAsync(data)
};
};

View File

@ -81,7 +81,7 @@ function DlgEditVersions() {
if (!isDirty || isProcessing || !isValid) {
return;
}
void versionUpdate(data).then(() => reset({ ...data }));
void versionUpdate({ itemID: itemID, version: data }).then(() => reset({ ...data }));
}
return (

View File

@ -2,6 +2,8 @@
* Module: Models for LibraryItem.
*/
import { z } from 'zod';
/**
* Represents type of library items.
*/
@ -31,16 +33,17 @@ export enum LocationHead {
export const BASIC_SCHEMAS = '/L/Базовые';
export const schemaVersionInfo = z.object({
id: z.coerce.number(),
version: z.string(),
description: z.string(),
time_create: z.string()
});
/**
* Represents library item version information.
*/
export interface IVersionInfo {
id: number;
item: number;
version: string;
description: string;
time_create: string;
}
export type IVersionInfo = z.infer<typeof schemaVersionInfo>;
/**
* Represents library item common data typical for all item types.
@ -70,7 +73,10 @@ export interface ILibraryItemData extends ILibraryItem {
/**
* Represents {@link ILibraryItem} minimal reference data.
*/
export interface ILibraryItemReference extends Pick<ILibraryItem, 'id' | 'alias'> {}
export interface ILibraryItemReference {
id: number;
alias: string;
}
/**
* Represents {@link ILibraryItem} extended data with versions.

View File

@ -1,6 +1,7 @@
import { queryOptions } from '@tanstack/react-query';
import { IConstituentaReference, ITargetCst } from '@/features/rsform/models/rsform';
import { ITargetCst } from '@/features/rsform/backend/types';
import { IConstituentaReference } from '@/features/rsform/models/rsform';
import { axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
import { DELAYS, KEYS } from '@/backend/configuration';

View File

@ -1,7 +1,5 @@
import { useMutation } from '@tanstack/react-query';
import { ITargetCst } from '@/features/rsform/models/rsform';
import { ossApi } from './api';
export const useFindPredecessor = () => {
@ -10,6 +8,6 @@ export const useFindPredecessor = () => {
mutationFn: ossApi.getPredecessor
});
return {
findPredecessor: (data: ITargetCst) => mutation.mutateAsync(data)
findPredecessor: (target: number) => mutation.mutateAsync({ target: target })
};
};

View File

@ -19,13 +19,13 @@ import { IRSFormDTO } from './types';
* based on the loaded data. It also establishes dependencies between concepts in the graph.
*/
export class RSFormLoader {
private schema: IRSFormDTO;
private schema: IRSForm;
private graph: Graph = new Graph();
private cstByAlias = new Map<string, IConstituenta>();
private cstByID = new Map<number, IConstituenta>();
constructor(input: IRSFormDTO) {
this.schema = input;
this.schema = input as unknown as IRSForm;
}
produceRSForm(): IRSForm {
@ -33,7 +33,7 @@ export class RSFormLoader {
this.createGraph();
this.inferCstAttributes();
const result = this.schema as IRSForm;
const result = this.schema;
result.stats = this.calculateStats();
result.graph = this.graph;
result.cstByAlias = this.cstByAlias;
@ -43,8 +43,8 @@ export class RSFormLoader {
private prepareLookups() {
this.schema.items.forEach(cst => {
this.cstByAlias.set(cst.alias, cst as IConstituenta);
this.cstByID.set(cst.id, cst as IConstituenta);
this.cstByAlias.set(cst.alias, cst);
this.cstByID.set(cst.id, cst);
this.graph.addNode(cst.id);
});
}
@ -189,7 +189,7 @@ export class RSFormLoader {
sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0),
0
),
count_inherited: items.reduce((sum, cst) => sum + ((cst as IConstituenta).is_inherited ? 1 : 0), 0),
count_inherited: items.reduce((sum, cst) => sum + (cst.is_inherited ? 1 : 0), 0),
count_text_term: items.reduce((sum, cst) => sum + (cst.term_raw ? 1 : 0), 0),
count_definition: items.reduce((sum, cst) => sum + (cst.definition_raw ? 1 : 0), 0),

View File

@ -4,21 +4,28 @@ import { axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
import { DELAYS, KEYS } from '@/backend/configuration';
import { infoMsg } from '@/utils/labels';
import { IConstituentaList, IConstituentaMeta, ITargetCst } from '../models/rsform';
import { IExpressionParse } from '../models/rslang';
import { IConstituentaList } from '../models/rsform';
import {
ICheckConstituentaDTO,
IConstituentaBasicsDTO,
ICstCreatedResponse,
ICstCreateDTO,
ICstMoveDTO,
ICstRenameDTO,
ICstSubstitutionsDTO,
ICstUpdateDTO,
IExpressionParseDTO,
IInlineSynthesisDTO,
IProduceStructureResponse,
IRSFormDTO,
IRSFormUploadDTO
IRSFormUploadDTO,
ITargetCst,
schemaConstituentaBasics,
schemaCstCreatedResponse,
schemaExpressionParse,
schemaProduceStructureResponse,
schemaRSForm
} from './types';
export const rsformsApi = {
@ -32,6 +39,7 @@ export const rsformsApi = {
!itemID
? undefined
: axiosGet<IRSFormDTO>({
schema: schemaRSForm,
endpoint: version ? `/api/library/${itemID}/versions/${version}` : `/api/rsforms/${itemID}/details`,
options: { signal: meta.signal }
})
@ -45,6 +53,7 @@ export const rsformsApi = {
}),
upload: (data: IRSFormUploadDTO) =>
axiosPatch<IRSFormUploadDTO, IRSFormDTO>({
schema: schemaRSForm,
endpoint: `/api/rsforms/${data.itemID}/load-trs`,
request: {
data: data,
@ -59,6 +68,7 @@ export const rsformsApi = {
cstCreate: ({ itemID, data }: { itemID: number; data: ICstCreateDTO }) =>
axiosPost<ICstCreateDTO, ICstCreatedResponse>({
schema: schemaCstCreatedResponse,
endpoint: `/api/rsforms/${itemID}/create-cst`,
request: {
data: data,
@ -66,7 +76,8 @@ export const rsformsApi = {
}
}),
cstUpdate: ({ itemID, data }: { itemID: number; data: ICstUpdateDTO }) =>
axiosPatch<ICstUpdateDTO, IConstituentaMeta>({
axiosPatch<ICstUpdateDTO, IConstituentaBasicsDTO>({
schema: schemaConstituentaBasics,
endpoint: `/api/rsforms/${itemID}/update-cst`,
request: {
data: data,
@ -75,6 +86,7 @@ export const rsformsApi = {
}),
cstDelete: ({ itemID, data }: { itemID: number; data: IConstituentaList }) =>
axiosPatch<IConstituentaList, IRSFormDTO>({
schema: schemaRSForm,
endpoint: `/api/rsforms/${itemID}/delete-multiple-cst`,
request: {
data: data,
@ -83,6 +95,7 @@ export const rsformsApi = {
}),
cstRename: ({ itemID, data }: { itemID: number; data: ICstRenameDTO }) =>
axiosPatch<ICstRenameDTO, ICstCreatedResponse>({
schema: schemaCstCreatedResponse,
endpoint: `/api/rsforms/${itemID}/rename-cst`,
request: {
data: data,
@ -91,6 +104,7 @@ export const rsformsApi = {
}),
cstSubstitute: ({ itemID, data }: { itemID: number; data: ICstSubstitutionsDTO }) =>
axiosPatch<ICstSubstitutionsDTO, IRSFormDTO>({
schema: schemaRSForm,
endpoint: `/api/rsforms/${itemID}/substitute`,
request: {
data: data,
@ -99,12 +113,14 @@ export const rsformsApi = {
}),
cstMove: ({ itemID, data }: { itemID: number; data: ICstMoveDTO }) =>
axiosPatch<ICstMoveDTO, IRSFormDTO>({
schema: schemaRSForm,
endpoint: `/api/rsforms/${itemID}/move-cst`,
request: { data: data }
}),
produceStructure: ({ itemID, data }: { itemID: number; data: ITargetCst }) =>
axiosPatch<ITargetCst, IProduceStructureResponse>({
schema: schemaProduceStructureResponse,
endpoint: `/api/rsforms/${itemID}/produce-structure`,
request: {
data: data,
@ -113,6 +129,7 @@ export const rsformsApi = {
}),
inlineSynthesis: (data: IInlineSynthesisDTO) =>
axiosPatch<IInlineSynthesisDTO, IRSFormDTO>({
schema: schemaRSForm,
endpoint: `/api/rsforms/inline-synthesis`,
request: {
data: data,
@ -121,17 +138,20 @@ export const rsformsApi = {
}),
restoreOrder: ({ itemID }: { itemID: number }) =>
axiosPatch<undefined, IRSFormDTO>({
schema: schemaRSForm,
endpoint: `/api/rsforms/${itemID}/restore-order`,
request: { successMessage: infoMsg.reorderComplete }
}),
resetAliases: ({ itemID }: { itemID: number }) =>
axiosPatch<undefined, IRSFormDTO>({
schema: schemaRSForm,
endpoint: `/api/rsforms/${itemID}/reset-aliases`,
request: { successMessage: infoMsg.reindexComplete }
}),
checkConstituenta: ({ itemID, data }: { itemID: number; data: ICheckConstituentaDTO }) =>
axiosPost<ICheckConstituentaDTO, IExpressionParse>({
axiosPost<ICheckConstituentaDTO, IExpressionParseDTO>({
schema: schemaExpressionParse,
endpoint: `/api/rsforms/${itemID}/check-constituenta`,
request: { data: data }
})

View File

@ -1,37 +1,22 @@
import { z } from 'zod';
import { ILibraryItemReference, ILibraryItemVersioned } from '@/features/library/models/library';
import { AccessPolicy, LibraryItemType, schemaVersionInfo } from '@/features/library/models/library';
import { errorMsg } from '@/utils/labels';
import { CstType, IConstituentaMeta, IInheritanceInfo } from '../models/rsform';
import { IArgumentInfo, ParsingStatus, ValueClass } from '../models/rslang';
import { CstType } from '../models/rsform';
import { ParsingStatus, RSErrorType, Syntax, TokenID, ValueClass } from '../models/rslang';
/**
* Represents {@link IConstituenta} data from server.
*/
export interface IConstituentaDTO extends IConstituentaMeta {
parse: {
status: ParsingStatus;
valueClass: ValueClass;
typification: string;
syntaxTree: string;
args: IArgumentInfo[];
};
}
/** Represents Constituenta basic persistent data. */
export type IConstituentaBasicsDTO = z.infer<typeof schemaConstituentaBasics>;
/**
* Represents data for {@link IRSForm} provided by backend.
*/
export interface IRSFormDTO extends ILibraryItemVersioned {
items: IConstituentaDTO[];
inheritance: IInheritanceInfo[];
oss: ILibraryItemReference[];
}
/** Represents {@link IConstituenta} data from server. */
export type IConstituentaDTO = z.infer<typeof schemaConstituenta>;
/**
* Represents data, used for uploading {@link IRSForm} as file.
*/
/** Represents data for {@link IRSForm} provided by backend. */
export type IRSFormDTO = z.infer<typeof schemaRSForm>;
/** Represents data, used for uploading {@link IRSForm} as file. */
export interface IRSFormUploadDTO {
itemID: number;
load_metadata: boolean;
@ -39,36 +24,128 @@ export interface IRSFormUploadDTO {
fileName: string;
}
/**
* Represents {@link IConstituenta} data, used in creation process.
*/
export const schemaCstCreate = z.object({
cst_type: z.nativeEnum(CstType),
alias: z.string().nonempty(errorMsg.requiredField),
convention: z.string(),
definition_formal: z.string(),
definition_raw: z.string(),
term_raw: z.string(),
term_forms: z.array(z.object({ text: z.string(), tags: z.string() })),
insert_after: z.number().nullable()
});
/**
* Represents {@link IConstituenta} data, used in creation process.
*/
export type ICstCreateDTO = z.infer<typeof schemaCstCreate>;
/**
* Represents data response when creating {@link IConstituenta}.
*/
export interface ICstCreatedResponse {
new_cst: IConstituentaMeta;
schema: IRSFormDTO;
/** Represents target {@link IConstituenta}. */
export interface ITargetCst {
target: number;
}
/**
* Represents data, used in updating persistent attributes in {@link IConstituenta}.
*/
/** Represents {@link IConstituenta} data, used in creation process. */
export type ICstCreateDTO = z.infer<typeof schemaCstCreate>;
/** Represents data response when creating {@link IConstituenta}. */
export type ICstCreatedResponse = z.infer<typeof schemaCstCreatedResponse>;
/** Represents data, used in updating persistent attributes in {@link IConstituenta}. */
export type ICstUpdateDTO = z.infer<typeof schemaCstUpdate>;
/** Represents data, used in renaming {@link IConstituenta}. */
export type ICstRenameDTO = z.infer<typeof schemaCstRename>;
/** Represents data, used in ordering a list of {@link IConstituenta}. */
export interface ICstMoveDTO {
items: number[];
move_to: number; // Note: 0-base index
}
/** Represents data response when creating producing structure of {@link IConstituenta}. */
export type IProduceStructureResponse = z.infer<typeof schemaProduceStructureResponse>;
/** Represents data, used in merging single {@link IConstituenta}. */
export type ICstSubstitute = z.infer<typeof schemaCstSubstitute>;
/** Represents input data for inline synthesis. */
export type IInlineSynthesisDTO = z.infer<typeof schemaInlineSynthesis>;
/** Represents {@link IConstituenta} data, used for checking expression. */
export interface ICheckConstituentaDTO {
alias: string;
cst_type: CstType;
definition_formal: string;
}
/** Represents data, used in merging multiple {@link IConstituenta}. */
export type ICstSubstitutionsDTO = z.infer<typeof schemaCstSubstitutions>;
/** Represents parsing error description. */
export type IRSErrorDescription = z.infer<typeof schemaRSErrorDescription>;
/** Represents results of expression parse in RSLang. */
export type IExpressionParseDTO = z.infer<typeof schemaExpressionParse>;
// ========= SCHEMAS ========
export const schemaConstituentaBasics = z.object({
id: z.coerce.number(),
alias: z.string().nonempty(errorMsg.requiredField),
convention: z.string(),
cst_type: z.nativeEnum(CstType),
definition_formal: z.string(),
definition_raw: z.string(),
definition_resolved: z.string(),
term_raw: z.string(),
term_resolved: z.string(),
term_forms: z.array(z.object({ text: z.string(), tags: z.string() }))
});
export const schemaConstituenta = schemaConstituentaBasics.extend({
parse: z.object({
status: z.nativeEnum(ParsingStatus),
valueClass: z.nativeEnum(ValueClass),
typification: z.string(),
syntaxTree: z.string(),
args: z.array(z.object({ alias: z.string(), typification: z.string() }))
})
});
export const schemaRSForm = z.object({
id: z.coerce.number(),
item_type: z.nativeEnum(LibraryItemType),
title: z.string(),
alias: z.string(),
comment: z.string(),
visible: z.boolean(),
read_only: z.boolean(),
location: z.string(),
access_policy: z.nativeEnum(AccessPolicy),
time_create: z.string(),
time_update: z.string(),
owner: z.coerce.number().nullable(),
editors: z.array(z.coerce.number()),
version: z.coerce.number().optional(),
versions: z.array(schemaVersionInfo),
items: z.array(schemaConstituenta),
inheritance: z.array(
z.object({
child: z.coerce.number(),
child_source: z.coerce.number(),
parent: z.coerce.number(),
parent_source: z.coerce.number()
})
),
oss: z.array(z.object({ id: z.coerce.number(), alias: z.string() }))
});
export const schemaCstCreate = schemaConstituentaBasics
.pick({
cst_type: true,
alias: true,
convention: true,
definition_formal: true,
definition_raw: true,
term_raw: true,
term_forms: true
})
.extend({
insert_after: z.number().nullable()
});
export const schemaCstCreatedResponse = z.object({
new_cst: schemaConstituentaBasics,
schema: schemaRSForm
});
export const schemaCstUpdate = z.object({
target: z.number(),
item_data: z.object({
@ -80,57 +157,26 @@ export const schemaCstUpdate = z.object({
})
});
/**
* Represents data, used in updating persistent attributes in {@link IConstituenta}.
*/
export type ICstUpdateDTO = z.infer<typeof schemaCstUpdate>;
/**
* Represents data, used in renaming {@link IConstituenta}.
*/
export const schemaCstRename = z.object({
target: z.number(),
alias: z.string(),
cst_type: z.nativeEnum(CstType)
});
/**
* Represents data, used in renaming {@link IConstituenta}.
*/
export type ICstRenameDTO = z.infer<typeof schemaCstRename>;
export const schemaProduceStructureResponse = z.object({
cst_list: z.array(z.number()),
schema: schemaRSForm
});
/**
* Represents data, used in ordering a list of {@link IConstituenta}.
*/
export interface ICstMoveDTO {
items: number[];
move_to: number; // Note: 0-base index
}
/**
* Represents data response when creating producing structure of {@link IConstituenta}.
*/
export interface IProduceStructureResponse {
cst_list: number[];
schema: IRSFormDTO;
}
/**
* Represents data, used in merging single {@link IConstituenta}.
*/
export const schemaCstSubstitute = z.object({
original: z.number(),
substitution: z.number()
});
/**
* Represents data, used in merging single {@link IConstituenta}.
*/
export type ICstSubstitute = z.infer<typeof schemaCstSubstitute>;
export const schemaCstSubstitutions = z.object({
substitutions: z.array(schemaCstSubstitute).min(1, { message: errorMsg.emptySubstitutions })
});
/**
* Represents input data for inline synthesis.
*/
export const schemaInlineSynthesis = z.object({
receiver: z.number(),
source: z.number().nullable(),
@ -138,28 +184,35 @@ export const schemaInlineSynthesis = z.object({
substitutions: z.array(schemaCstSubstitute)
});
/**
* Represents input data for inline synthesis.
*/
export type IInlineSynthesisDTO = z.infer<typeof schemaInlineSynthesis>;
/**
* Represents {@link IConstituenta} data, used for checking expression.
*/
export interface ICheckConstituentaDTO {
alias: string;
cst_type: CstType;
definition_formal: string;
}
/**
* Represents data, used in renaming {@link IConstituenta}.
*/
export const schemaCstSubstitutions = z.object({
substitutions: z.array(schemaCstSubstitute).min(1, { message: errorMsg.emptySubstitutions })
export const schemaRSErrorDescription = z.object({
errorType: z.nativeEnum(RSErrorType),
position: z.number(),
isCritical: z.boolean(),
params: z.array(z.string())
});
/**
* Represents data, used in merging multiple {@link IConstituenta}.
*/
export type ICstSubstitutionsDTO = z.infer<typeof schemaCstSubstitutions>;
export const schemaExpressionParse = z.object({
parseResult: z.boolean(),
prefixLen: z.number(),
syntax: z.nativeEnum(Syntax),
typification: z.string(),
valueClass: z.nativeEnum(ValueClass),
errors: z.array(schemaRSErrorDescription),
astText: z.string(),
ast: z.array(
z.object({
uid: z.number(),
parent: z.number(),
typeID: z.nativeEnum(TokenID),
start: z.number(),
finish: z.number(),
data: z.object({ dataType: z.string(), value: z.unknown().refine(value => value !== undefined) })
})
),
args: z.array(
z.object({
alias: z.string(),
typification: z.string()
})
)
});

View File

@ -4,9 +4,8 @@ import { useUpdateTimestamp } from '@/features/library';
import { KEYS } from '@/backend/configuration';
import { ITargetCst } from '../models/rsform';
import { rsformsApi } from './api';
import { ITargetCst } from './types';
export const useProduceStructure = () => {
const client = useQueryClient();

View File

@ -7,9 +7,9 @@ import { ModalForm } from '@/components/Modal';
import { useDialogsStore } from '@/stores/dialogs';
import { errorMsg } from '@/utils/labels';
import { ICstCreateDTO, schemaCstCreate } from '../../backend/types';
import { IConstituentaBasicsDTO, ICstCreateDTO, schemaCstCreate } from '../../backend/types';
import { useCstCreate } from '../../backend/useCstCreate';
import { IConstituentaMeta, IRSForm } from '../../models/rsform';
import { IRSForm } from '../../models/rsform';
import { validateNewAlias } from '../../models/rsformAPI';
import FormCreateCst from './FormCreateCst';
@ -17,7 +17,7 @@ import FormCreateCst from './FormCreateCst';
export interface DlgCreateCstProps {
initial: ICstCreateDTO;
schema: IRSForm;
onCreate: (data: IConstituentaMeta) => void;
onCreate: (data: IConstituentaBasicsDTO) => void;
}
function DlgCreateCst() {
@ -30,7 +30,7 @@ function DlgCreateCst() {
});
const alias = useWatch({ control: methods.control, name: 'alias' });
const cst_type = useWatch({ control: methods.control, name: 'cst_type' });
const isValid = alias !== initial.alias && validateNewAlias(alias, cst_type, schema);
const isValid = validateNewAlias(alias, cst_type, schema);
function onSubmit(data: ICstCreateDTO) {
return cstCreate({ itemID: schema.id, data }).then(onCreate);

View File

@ -12,9 +12,9 @@ import { ModalForm } from '@/components/Modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { ICstCreateDTO, schemaCstCreate } from '../../backend/types';
import { IConstituentaBasicsDTO, ICstCreateDTO, schemaCstCreate } from '../../backend/types';
import { useCstCreate } from '../../backend/useCstCreate';
import { CstType, IConstituentaMeta, IRSForm } from '../../models/rsform';
import { CstType, IRSForm } from '../../models/rsform';
import { generateAlias, validateNewAlias } from '../../models/rsformAPI';
import FormCreateCst from '../DlgCreateCst/FormCreateCst';
@ -24,7 +24,7 @@ import { TemplateState } from './TemplateContext';
export interface DlgCstTemplateProps {
schema: IRSForm;
onCreate: (data: IConstituentaMeta) => void;
onCreate: (data: IConstituentaBasicsDTO) => void;
insertAfter?: number;
}

View File

@ -5,16 +5,10 @@
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
import { IRSErrorDescription } from './backend/types';
import { GramData, Grammeme, ReferenceType } from './models/language';
import { CstClass, CstType, ExpressionStatus, IConstituenta, IRSForm } from './models/rsform';
import {
IArgumentInfo,
IRSErrorDescription,
ISyntaxTreeNode,
ParsingStatus,
RSErrorType,
TokenID
} from './models/rslang';
import { IArgumentInfo, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from './models/rslang';
import { CstMatchMode, DependencyMode } from './stores/cstSearch';
import { GraphColoring } from './stores/termGraph';

View File

@ -56,9 +56,9 @@ export interface TermForm {
}
/**
* Represents Constituenta basic persistent data.
* Represents Constituenta.
*/
export interface IConstituentaMeta {
export interface IConstituenta {
id: number;
alias: string;
convention: string;
@ -69,19 +69,7 @@ export interface IConstituentaMeta {
term_raw: string;
term_resolved: string;
term_forms: TermForm[];
}
/**
* Represents target {@link IConstituenta}.
*/
export interface ITargetCst {
target: number;
}
/**
* Represents Constituenta.
*/
export interface IConstituenta extends IConstituentaMeta {
parse: {
status: ParsingStatus;
valueClass: ValueClass;
@ -127,7 +115,10 @@ export interface IConstituenta extends IConstituentaMeta {
/**
* Represents {@link IConstituenta} reference.
*/
export interface IConstituentaReference extends Pick<IConstituenta, 'id' | 'schema'> {}
export interface IConstituentaReference {
id: number;
schema: number;
}
/**
* Represents Constituenta list.
@ -183,12 +174,3 @@ export interface IRSForm extends ILibraryItemVersioned {
cstByAlias: Map<string, IConstituenta>;
cstByID: Map<number, IConstituenta>;
}
/**
* Represents single substitution for binary synthesis table.
*/
export interface IBinarySubstitution {
leftCst: IConstituenta;
rightCst: IConstituenta;
deleteRight: boolean;
}

View File

@ -34,16 +34,6 @@ export enum ParsingStatus {
INCORRECT = 'incorrect'
}
/**
* Represents parsing error description.
*/
export interface IRSErrorDescription {
errorType: RSErrorType;
position: number;
isCritical: boolean;
params: string[];
}
/**
* Represents AST node.
*/
@ -86,21 +76,6 @@ export interface IArgumentValue extends IArgumentInfo {
value?: string;
}
/**
* Represents results of expression parse in RSLang.
*/
export interface IExpressionParse {
parseResult: boolean;
prefixLen: number;
syntax: Syntax;
typification: string;
valueClass: ValueClass;
errors: IRSErrorDescription[];
astText: string;
ast: SyntaxTree;
args: IArgumentInfo[];
}
/**
* Represents RSLang token types.
*/

View File

@ -7,8 +7,10 @@ import { Tree } from '@lezer/common';
import { cursorNode } from '@/utils/codemirror';
import { PARAMETER } from '@/utils/constants';
import { IRSErrorDescription } from '../backend/types';
import { CstType } from './rsform';
import { AliasMapping, IArgumentValue, IRSErrorDescription, RSErrorClass, RSErrorType, SyntaxTree } from './rslang';
import { AliasMapping, IArgumentValue, RSErrorClass, RSErrorType, SyntaxTree } from './rslang';
// cspell:disable
const LOCALS_REGEXP = /[_a-zα-ω][a-zα-ω]*\d*/g;

View File

@ -16,14 +16,14 @@ import { useDialogsStore } from '@/stores/dialogs';
import { useModificationStore } from '@/stores/modification';
import { errorMsg } from '@/utils/labels';
import { ICstUpdateDTO, schemaCstUpdate } from '../../../backend/types';
import { ICstUpdateDTO, IExpressionParseDTO, schemaCstUpdate } from '../../../backend/types';
import { useCstUpdate } from '../../../backend/useCstUpdate';
import { useMutatingRSForm } from '../../../backend/useMutatingRSForm';
import { RefsInput } from '../../../components/RefsInput';
import { labelCstTypification, labelTypification } from '../../../labels';
import { CstType, IConstituenta, IRSForm } from '../../../models/rsform';
import { isBaseSet, isBasicConcept, isFunctional } from '../../../models/rsformAPI';
import { IExpressionParse, ParsingStatus } from '../../../models/rslang';
import { ParsingStatus } from '../../../models/rslang';
import EditorRSExpression from '../EditorRSExpression';
interface FormConstituentaProps {
@ -50,7 +50,7 @@ function FormConstituenta({ disabled, id, toggleReset, schema, activeCst, onOpen
formState: { isDirty }
} = useForm<ICstUpdateDTO>({ resolver: zodResolver(schemaCstUpdate) });
const [localParse, setLocalParse] = useState<IExpressionParse | undefined>(undefined);
const [localParse, setLocalParse] = useState<IExpressionParseDTO | undefined>(undefined);
const typification = useMemo(
() =>

View File

@ -56,7 +56,7 @@ function ToolbarConstituenta({
const isProcessing = useMutatingRSForm();
function viewPredecessor(target: number) {
void findPredecessor({ target: target }).then(reference =>
void findPredecessor(target).then(reference =>
router.push(
urls.schema_props({
id: reference.schema,

View File

@ -5,6 +5,7 @@ import { toast } from 'react-toastify';
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
import { BadgeHelp, HelpTopic } from '@/features/help';
import { IExpressionParseDTO } from '@/features/rsform/backend/types';
import { DataCallback } from '@/backend/apiTransport';
import { Overlay } from '@/components/Container';
@ -13,7 +14,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { usePreferencesStore } from '@/stores/preferences';
import { errorMsg } from '@/utils/labels';
import { ICheckConstituentaDTO } from '../../../backend/types';
import { ICheckConstituentaDTO, IRSErrorDescription } from '../../../backend/types';
import { useCheckConstituenta } from '../../../backend/useCheckConstituenta';
import { useMutatingRSForm } from '../../../backend/useMutatingRSForm';
import RSInput from '../../../components/RSInput';
@ -21,7 +22,7 @@ import { parser as rslangParser } from '../../../components/RSInput/rslang/parse
import { RSTextWrapper } from '../../../components/RSInput/textEditing';
import { IConstituenta } from '../../../models/rsform';
import { getDefinitionPrefix } from '../../../models/rsformAPI';
import { IExpressionParse, IRSErrorDescription, TokenID } from '../../../models/rslang';
import { TokenID } from '../../../models/rslang';
import { transformAST } from '../../../models/rslangAPI';
import { useRSEdit } from '../RSEditContext';
@ -42,7 +43,7 @@ interface EditorRSExpressionProps {
disabled?: boolean;
toggleReset?: boolean;
onChangeLocalParse: (typification: IExpressionParse | undefined) => void;
onChangeLocalParse: (typification: IExpressionParseDTO | undefined) => void;
onOpenEdit?: (cstID: number) => void;
onShowTypeGraph: (event: CProps.EventMouse) => void;
}
@ -62,7 +63,7 @@ function EditorRSExpression({
const [isModified, setIsModified] = useState(false);
const rsInput = useRef<ReactCodeMirrorRef>(null);
const [parseData, setParseData] = useState<IExpressionParse | undefined>(undefined);
const [parseData, setParseData] = useState<IExpressionParseDTO | undefined>(undefined);
const isProcessing = useMutatingRSForm();
const showControls = usePreferencesStore(state => state.showExpressionControls);
@ -70,7 +71,11 @@ function EditorRSExpression({
const { checkConstituenta: checkInternal, isPending } = useCheckConstituenta();
function checkConstituenta(expression: string, activeCst: IConstituenta, onSuccess?: DataCallback<IExpressionParse>) {
function checkConstituenta(
expression: string,
activeCst: IConstituenta,
onSuccess?: DataCallback<IExpressionParseDTO>
) {
const data: ICheckConstituentaDTO = {
definition_formal: expression,
alias: activeCst.alias,
@ -92,7 +97,7 @@ function EditorRSExpression({
setIsModified(newValue !== activeCst.definition_formal);
}
function handleCheckExpression(callback?: (parse: IExpressionParse) => void) {
function handleCheckExpression(callback?: (parse: IExpressionParseDTO) => void) {
checkConstituenta(value, activeCst, parse => {
onChangeLocalParse(parse);
if (parse.errors.length > 0) {

View File

@ -1,9 +1,9 @@
import { IExpressionParseDTO, IRSErrorDescription } from '../../../backend/types';
import { describeRSError } from '../../../labels';
import { IExpressionParse, IRSErrorDescription } from '../../../models/rslang';
import { getRSErrorPrefix } from '../../../models/rslangAPI';
interface ParsingResultProps {
data: IExpressionParse | undefined;
data: IExpressionParseDTO | undefined;
disabled?: boolean;
isOpen: boolean;
onShowError: (error: IRSErrorDescription) => void;

View File

@ -8,16 +8,17 @@ import { APP_COLORS } from '@/styling/colors';
import { globals } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
import { IExpressionParseDTO } from '../../../backend/types';
import { colorStatusBar } from '../../../colors';
import { labelExpressionStatus } from '../../../labels';
import { ExpressionStatus, IConstituenta } from '../../../models/rsform';
import { inferStatus } from '../../../models/rsformAPI';
import { IExpressionParse, ParsingStatus } from '../../../models/rslang';
import { ParsingStatus } from '../../../models/rslang';
interface StatusBarProps {
processing?: boolean;
isModified?: boolean;
parseData?: IExpressionParse;
parseData?: IExpressionParseDTO;
activeCst: IConstituenta;
onAnalyze: () => void;
}

View File

@ -14,11 +14,11 @@ import { PARAMETER, prefixes } from '@/utils/constants';
import { promptText } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils';
import { ICstCreateDTO } from '../../backend/types';
import { IConstituentaBasicsDTO, ICstCreateDTO } from '../../backend/types';
import { useCstCreate } from '../../backend/useCstCreate';
import { useCstMove } from '../../backend/useCstMove';
import { useRSFormSuspense } from '../../backend/useRSForm';
import { CstType, IConstituenta, IConstituentaMeta, IRSForm } from '../../models/rsform';
import { CstType, IConstituenta, IRSForm } from '../../models/rsform';
import { generateAlias } from '../../models/rsformAPI';
export enum RSTabID {
@ -177,7 +177,7 @@ export const RSEditState = ({
});
}
function onCreateCst(newCst: IConstituentaMeta) {
function onCreateCst(newCst: IConstituentaBasicsDTO) {
setSelected([newCst.id]);
navigateRSForm({ tab: activeTab, activeID: newCst.id });
if (activeTab === RSTabID.CST_LIST) {

View File

@ -141,7 +141,8 @@ export const errorMsg = {
invalidLocation: 'Некорректный формат пути',
versionTaken: 'Версия с таким шифром уже существует',
emptySubstitutions: 'Выберите хотя бы одно отождествление',
aliasInvalid: 'Введите незанятое имя, соответствующее типу'
aliasInvalid: 'Введите незанятое имя, соответствующее типу',
invalidResponse: 'Некорректный ответ сервера'
};
/**