From 75c0a6f848391f8137eb14af66eec147098eaf06 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Mon, 17 Feb 2025 14:31:17 +0300 Subject: [PATCH] F: Add zod validation for backend responses for rsforms --- .../frontend/src/backend/apiTransport.ts | 55 +++- .../frontend/src/components/InfoError.tsx | 10 +- .../src/features/library/backend/api.ts | 6 +- .../library/backend/useVersionUpdate.tsx | 6 +- .../DlgEditVersions/DlgEditVersions.tsx | 2 +- .../src/features/library/models/library.ts | 22 +- .../frontend/src/features/oss/backend/api.ts | 3 +- .../oss/backend/useFindPredecessor.tsx | 4 +- .../features/rsform/backend/RSFormLoader.ts | 12 +- .../src/features/rsform/backend/api.ts | 30 +- .../src/features/rsform/backend/schemas.ts | 0 .../src/features/rsform/backend/types.ts | 283 +++++++++++------- .../rsform/backend/useProduceStructure.tsx | 3 +- .../dialogs/DlgCreateCst/DlgCreateCst.tsx | 8 +- .../dialogs/DlgCstTemplate/DlgCstTemplate.tsx | 6 +- .../frontend/src/features/rsform/labels.ts | 10 +- .../src/features/rsform/models/rsform.ts | 30 +- .../src/features/rsform/models/rslang.ts | 25 -- .../src/features/rsform/models/rslangAPI.ts | 4 +- .../EditorConstituenta/FormConstituenta.tsx | 6 +- .../ToolbarConstituenta.tsx | 2 +- .../EditorRSExpression/EditorRSExpression.tsx | 17 +- .../EditorRSExpression/ParsingResult.tsx | 4 +- .../EditorRSExpression/StatusBar.tsx | 5 +- .../rsform/pages/RSFormPage/RSEditContext.tsx | 6 +- rsconcept/frontend/src/utils/labels.ts | 3 +- 26 files changed, 318 insertions(+), 244 deletions(-) create mode 100644 rsconcept/frontend/src/features/rsform/backend/schemas.ts diff --git a/rsconcept/frontend/src/backend/apiTransport.ts b/rsconcept/frontend/src/backend/apiTransport.ts index 790a1747..bd0a0c10 100644 --- a/rsconcept/frontend/src/backend/apiTransport.ts +++ b/rsconcept/frontend/src/backend/apiTransport.ts @@ -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 { endpoint: string; request?: IFrontRequest; options?: AxiosRequestConfig; + schema?: z.ZodType; } export interface IAxiosGetRequest { endpoint: string; options?: AxiosRequestConfig; signal?: AbortSignal; + schema?: z.ZodType; } // ================ Transport API calls ================ -export function axiosGet({ endpoint, options }: IAxiosGetRequest) { +export function axiosGet({ endpoint, options, schema }: IAxiosGetRequest) { return axiosInstance .get(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({ endpoint, options }: IAxiosGetRequest) export function axiosPost({ endpoint, request, - options + options, + schema }: IAxiosRequest) { return axiosInstance .post(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({ } 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({ export function axiosDelete({ endpoint, request, - options + options, + schema }: IAxiosRequest) { return axiosInstance .delete(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({ } 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({ export function axiosPatch({ endpoint, request, - options + options, + schema }: IAxiosRequest) { return axiosInstance .patch(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({ } 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; }); diff --git a/rsconcept/frontend/src/components/InfoError.tsx b/rsconcept/frontend/src/components/InfoError.tsx index 8f3f007c..5dea14c0 100644 --- a/rsconcept/frontend/src/components/InfoError.tsx +++ b/rsconcept/frontend/src/components/InfoError.tsx @@ -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

Ошибки отсутствуют

; } else if (typeof error === 'string') { return

{error}

; + } else if (error instanceof ZodError) { + return ( +
+

Ошибка валидации данных

+ ; +
+ ); } else if (!isAxiosError(error)) { return (
diff --git a/rsconcept/frontend/src/features/library/backend/api.ts b/rsconcept/frontend/src/features/library/backend/api.ts index b04e1131..9648d152 100644 --- a/rsconcept/frontend/src/features/library/backend/api.ts +++ b/rsconcept/frontend/src/features/library/backend/api.ts @@ -138,11 +138,11 @@ export const libraryApi = { successMessage: infoMsg.versionRestored } }), - versionUpdate: (data: IVersionUpdateDTO) => + versionUpdate: (data: { itemID: number; version: IVersionUpdateDTO }) => axiosPatch({ - endpoint: `/api/versions/${data.id}`, + endpoint: `/api/versions/${data.version.id}`, request: { - data: data, + data: data.version, successMessage: infoMsg.changesSaved } }), diff --git a/rsconcept/frontend/src/features/library/backend/useVersionUpdate.tsx b/rsconcept/frontend/src/features/library/backend/useVersionUpdate.tsx index 491e0da4..ae8a7301 100644 --- a/rsconcept/frontend/src/features/library/backend/useVersionUpdate.tsx +++ b/rsconcept/frontend/src/features/library/backend/useVersionUpdate.tsx @@ -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) }; }; diff --git a/rsconcept/frontend/src/features/library/dialogs/DlgEditVersions/DlgEditVersions.tsx b/rsconcept/frontend/src/features/library/dialogs/DlgEditVersions/DlgEditVersions.tsx index 8a25db8c..716b978c 100644 --- a/rsconcept/frontend/src/features/library/dialogs/DlgEditVersions/DlgEditVersions.tsx +++ b/rsconcept/frontend/src/features/library/dialogs/DlgEditVersions/DlgEditVersions.tsx @@ -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 ( diff --git a/rsconcept/frontend/src/features/library/models/library.ts b/rsconcept/frontend/src/features/library/models/library.ts index 418080c5..a03292e7 100644 --- a/rsconcept/frontend/src/features/library/models/library.ts +++ b/rsconcept/frontend/src/features/library/models/library.ts @@ -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; /** * 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 {} +export interface ILibraryItemReference { + id: number; + alias: string; +} /** * Represents {@link ILibraryItem} extended data with versions. diff --git a/rsconcept/frontend/src/features/oss/backend/api.ts b/rsconcept/frontend/src/features/oss/backend/api.ts index 4b86d0c2..5ebef418 100644 --- a/rsconcept/frontend/src/features/oss/backend/api.ts +++ b/rsconcept/frontend/src/features/oss/backend/api.ts @@ -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'; diff --git a/rsconcept/frontend/src/features/oss/backend/useFindPredecessor.tsx b/rsconcept/frontend/src/features/oss/backend/useFindPredecessor.tsx index ffef8502..52ec48c7 100644 --- a/rsconcept/frontend/src/features/oss/backend/useFindPredecessor.tsx +++ b/rsconcept/frontend/src/features/oss/backend/useFindPredecessor.tsx @@ -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 }) }; }; diff --git a/rsconcept/frontend/src/features/rsform/backend/RSFormLoader.ts b/rsconcept/frontend/src/features/rsform/backend/RSFormLoader.ts index e2e3f1f5..08e29570 100644 --- a/rsconcept/frontend/src/features/rsform/backend/RSFormLoader.ts +++ b/rsconcept/frontend/src/features/rsform/backend/RSFormLoader.ts @@ -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(); private cstByID = new Map(); 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), diff --git a/rsconcept/frontend/src/features/rsform/backend/api.ts b/rsconcept/frontend/src/features/rsform/backend/api.ts index 9affecd4..375eeb8e 100644 --- a/rsconcept/frontend/src/features/rsform/backend/api.ts +++ b/rsconcept/frontend/src/features/rsform/backend/api.ts @@ -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({ + 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({ + 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({ + 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({ + axiosPatch({ + 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({ + 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({ + 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({ + 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({ + schema: schemaRSForm, endpoint: `/api/rsforms/${itemID}/move-cst`, request: { data: data } }), produceStructure: ({ itemID, data }: { itemID: number; data: ITargetCst }) => axiosPatch({ + schema: schemaProduceStructureResponse, endpoint: `/api/rsforms/${itemID}/produce-structure`, request: { data: data, @@ -113,6 +129,7 @@ export const rsformsApi = { }), inlineSynthesis: (data: IInlineSynthesisDTO) => axiosPatch({ + schema: schemaRSForm, endpoint: `/api/rsforms/inline-synthesis`, request: { data: data, @@ -121,17 +138,20 @@ export const rsformsApi = { }), restoreOrder: ({ itemID }: { itemID: number }) => axiosPatch({ + schema: schemaRSForm, endpoint: `/api/rsforms/${itemID}/restore-order`, request: { successMessage: infoMsg.reorderComplete } }), resetAliases: ({ itemID }: { itemID: number }) => axiosPatch({ + schema: schemaRSForm, endpoint: `/api/rsforms/${itemID}/reset-aliases`, request: { successMessage: infoMsg.reindexComplete } }), checkConstituenta: ({ itemID, data }: { itemID: number; data: ICheckConstituentaDTO }) => - axiosPost({ + axiosPost({ + schema: schemaExpressionParse, endpoint: `/api/rsforms/${itemID}/check-constituenta`, request: { data: data } }) diff --git a/rsconcept/frontend/src/features/rsform/backend/schemas.ts b/rsconcept/frontend/src/features/rsform/backend/schemas.ts new file mode 100644 index 00000000..e69de29b diff --git a/rsconcept/frontend/src/features/rsform/backend/types.ts b/rsconcept/frontend/src/features/rsform/backend/types.ts index 0d281b91..c5fd4ffe 100644 --- a/rsconcept/frontend/src/features/rsform/backend/types.ts +++ b/rsconcept/frontend/src/features/rsform/backend/types.ts @@ -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; -/** - * 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; -/** - * Represents data, used for uploading {@link IRSForm} as file. - */ +/** Represents data for {@link IRSForm} provided by backend. */ +export type IRSFormDTO = z.infer; + +/** 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; - -/** - * 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; + +/** Represents data response when creating {@link IConstituenta}. */ +export type ICstCreatedResponse = z.infer; + +/** Represents data, used in updating persistent attributes in {@link IConstituenta}. */ +export type ICstUpdateDTO = z.infer; + +/** Represents data, used in renaming {@link IConstituenta}. */ +export type ICstRenameDTO = z.infer; + +/** 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; + +/** Represents data, used in merging single {@link IConstituenta}. */ +export type ICstSubstitute = z.infer; + +/** Represents input data for inline synthesis. */ +export type IInlineSynthesisDTO = z.infer; + +/** 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; + +/** Represents parsing error description. */ +export type IRSErrorDescription = z.infer; + +/** Represents results of expression parse in RSLang. */ +export type IExpressionParseDTO = z.infer; + +// ========= 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; - -/** - * 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; +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; +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; - -/** - * 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; +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() + }) + ) +}); diff --git a/rsconcept/frontend/src/features/rsform/backend/useProduceStructure.tsx b/rsconcept/frontend/src/features/rsform/backend/useProduceStructure.tsx index c267c4aa..f1f67c1d 100644 --- a/rsconcept/frontend/src/features/rsform/backend/useProduceStructure.tsx +++ b/rsconcept/frontend/src/features/rsform/backend/useProduceStructure.tsx @@ -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(); diff --git a/rsconcept/frontend/src/features/rsform/dialogs/DlgCreateCst/DlgCreateCst.tsx b/rsconcept/frontend/src/features/rsform/dialogs/DlgCreateCst/DlgCreateCst.tsx index 63fecc0b..e8805d97 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/DlgCreateCst/DlgCreateCst.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/DlgCreateCst/DlgCreateCst.tsx @@ -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); diff --git a/rsconcept/frontend/src/features/rsform/dialogs/DlgCstTemplate/DlgCstTemplate.tsx b/rsconcept/frontend/src/features/rsform/dialogs/DlgCstTemplate/DlgCstTemplate.tsx index b8097e7b..e7c490a7 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/DlgCstTemplate/DlgCstTemplate.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/DlgCstTemplate/DlgCstTemplate.tsx @@ -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; } diff --git a/rsconcept/frontend/src/features/rsform/labels.ts b/rsconcept/frontend/src/features/rsform/labels.ts index 53b08204..027d6a5b 100644 --- a/rsconcept/frontend/src/features/rsform/labels.ts +++ b/rsconcept/frontend/src/features/rsform/labels.ts @@ -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'; diff --git a/rsconcept/frontend/src/features/rsform/models/rsform.ts b/rsconcept/frontend/src/features/rsform/models/rsform.ts index f16b562f..a2bc7a5f 100644 --- a/rsconcept/frontend/src/features/rsform/models/rsform.ts +++ b/rsconcept/frontend/src/features/rsform/models/rsform.ts @@ -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 {} +export interface IConstituentaReference { + id: number; + schema: number; +} /** * Represents Constituenta list. @@ -183,12 +174,3 @@ export interface IRSForm extends ILibraryItemVersioned { cstByAlias: Map; cstByID: Map; } - -/** - * Represents single substitution for binary synthesis table. - */ -export interface IBinarySubstitution { - leftCst: IConstituenta; - rightCst: IConstituenta; - deleteRight: boolean; -} diff --git a/rsconcept/frontend/src/features/rsform/models/rslang.ts b/rsconcept/frontend/src/features/rsform/models/rslang.ts index 8081fcb4..f892c8a0 100644 --- a/rsconcept/frontend/src/features/rsform/models/rslang.ts +++ b/rsconcept/frontend/src/features/rsform/models/rslang.ts @@ -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. */ diff --git a/rsconcept/frontend/src/features/rsform/models/rslangAPI.ts b/rsconcept/frontend/src/features/rsform/models/rslangAPI.ts index c9bf944f..b3ade302 100644 --- a/rsconcept/frontend/src/features/rsform/models/rslangAPI.ts +++ b/rsconcept/frontend/src/features/rsform/models/rslangAPI.ts @@ -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; diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx index 661027b3..8b4e6eef 100644 --- a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx @@ -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({ resolver: zodResolver(schemaCstUpdate) }); - const [localParse, setLocalParse] = useState(undefined); + const [localParse, setLocalParse] = useState(undefined); const typification = useMemo( () => diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorConstituenta/ToolbarConstituenta.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorConstituenta/ToolbarConstituenta.tsx index ecad06e3..6dc9537d 100644 --- a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorConstituenta/ToolbarConstituenta.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorConstituenta/ToolbarConstituenta.tsx @@ -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, diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx index a6f7423a..2d3cef7f 100644 --- a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx @@ -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(null); - const [parseData, setParseData] = useState(undefined); + const [parseData, setParseData] = useState(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) { + function checkConstituenta( + expression: string, + activeCst: IConstituenta, + onSuccess?: DataCallback + ) { 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) { diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/ParsingResult.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/ParsingResult.tsx index e4c24d96..9df598a5 100644 --- a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/ParsingResult.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/ParsingResult.tsx @@ -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; diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/StatusBar.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/StatusBar.tsx index 4fc03de4..a57c4664 100644 --- a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/StatusBar.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorRSExpression/StatusBar.tsx @@ -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; } diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/RSEditContext.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/RSEditContext.tsx index ca1258c6..c0430f8d 100644 --- a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/RSEditContext.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/RSEditContext.tsx @@ -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) { diff --git a/rsconcept/frontend/src/utils/labels.ts b/rsconcept/frontend/src/utils/labels.ts index 0dd8c57e..2a252451 100644 --- a/rsconcept/frontend/src/utils/labels.ts +++ b/rsconcept/frontend/src/utils/labels.ts @@ -141,7 +141,8 @@ export const errorMsg = { invalidLocation: 'Некорректный формат пути', versionTaken: 'Версия с таким шифром уже существует', emptySubstitutions: 'Выберите хотя бы одно отождествление', - aliasInvalid: 'Введите незанятое имя, соответствующее типу' + aliasInvalid: 'Введите незанятое имя, соответствующее типу', + invalidResponse: 'Некорректный ответ сервера' }; /**