feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
124
frontend/infra/idl/idl-parser/src/common/extension_type.ts
Normal file
124
frontend/infra/idl/idl-parser/src/common/extension_type.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const POSITIONS = [
|
||||
'query',
|
||||
'body',
|
||||
'path',
|
||||
'header',
|
||||
'entire_body',
|
||||
'raw_body',
|
||||
'status_code',
|
||||
];
|
||||
|
||||
export const SERIALIZERS = ['json', 'form', 'urlencoded'];
|
||||
|
||||
export const UPPERCASE_METHODS = [
|
||||
'GET',
|
||||
'POST',
|
||||
'PUT',
|
||||
'DELETE',
|
||||
'PATCH',
|
||||
'HEAD',
|
||||
];
|
||||
|
||||
export const LOWERCASE_METHODS = [
|
||||
'get',
|
||||
'post',
|
||||
'put',
|
||||
'delete',
|
||||
'patch',
|
||||
'head',
|
||||
];
|
||||
|
||||
export const SERVICE_EXTENSTION_CONFIG_KEYS = ['uri_prefix'];
|
||||
|
||||
export const FUNCTION_EXTENSTION_CONFIG_KEYS = [
|
||||
'serializer',
|
||||
'uri',
|
||||
'method',
|
||||
'group',
|
||||
'custom',
|
||||
'version',
|
||||
...LOWERCASE_METHODS,
|
||||
];
|
||||
|
||||
export const FIELD_EXTENSTION_CONFIG_KEYS = [
|
||||
'position',
|
||||
'key',
|
||||
'web_type',
|
||||
'value_type',
|
||||
'tag',
|
||||
...POSITIONS,
|
||||
];
|
||||
|
||||
export type Position =
|
||||
| 'query'
|
||||
| 'body'
|
||||
| 'path'
|
||||
| 'header'
|
||||
| 'entire_body'
|
||||
| 'raw_body'
|
||||
| 'status_code';
|
||||
|
||||
export type Serializer = 'json' | 'form' | 'urlencoded';
|
||||
|
||||
export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
|
||||
|
||||
export interface ServiceExtensionConfig {
|
||||
uri_prefix?: string;
|
||||
}
|
||||
|
||||
export interface FunctionExtensionConfig {
|
||||
serializer?: Serializer;
|
||||
uri?: string;
|
||||
method?: Method;
|
||||
group?: string;
|
||||
custom?: string;
|
||||
// NOTE: used in bytedance
|
||||
version?: string;
|
||||
|
||||
get?: string;
|
||||
post?: string;
|
||||
put?: string;
|
||||
delete?: string;
|
||||
}
|
||||
|
||||
export interface FieldExtensionConfig {
|
||||
position?: Position;
|
||||
key?: string;
|
||||
web_type?: string;
|
||||
value_type?: string;
|
||||
|
||||
query?: string;
|
||||
body?: string;
|
||||
path?: string;
|
||||
header?: string;
|
||||
entire_body?: string;
|
||||
raw_body?: string;
|
||||
status_code?: string;
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
export interface ExtensionConfig
|
||||
extends ServiceExtensionConfig,
|
||||
FunctionExtensionConfig,
|
||||
FieldExtensionConfig {}
|
||||
|
||||
export type ExtensionConfigStringKey = Exclude<
|
||||
keyof ExtensionConfig,
|
||||
'serializer' | 'method' | 'position'
|
||||
>;
|
||||
203
frontend/infra/idl/idl-parser/src/common/extension_util.ts
Normal file
203
frontend/infra/idl/idl-parser/src/common/extension_util.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
POSITIONS,
|
||||
SERIALIZERS,
|
||||
UPPERCASE_METHODS,
|
||||
LOWERCASE_METHODS,
|
||||
SERVICE_EXTENSTION_CONFIG_KEYS,
|
||||
FUNCTION_EXTENSTION_CONFIG_KEYS,
|
||||
FIELD_EXTENSTION_CONFIG_KEYS,
|
||||
type ExtensionConfig,
|
||||
type ExtensionConfigStringKey,
|
||||
type FieldExtensionConfig,
|
||||
type FunctionExtensionConfig,
|
||||
type ServiceExtensionConfig,
|
||||
type Serializer,
|
||||
type Method,
|
||||
type Position,
|
||||
} from './extension_type';
|
||||
|
||||
const goJsonTagRegExp = /^\s*json:(\\?[',"])(.*?)\1/;
|
||||
|
||||
function getTag(newtag = '', inputStr = '') {
|
||||
const tags: string[] = [];
|
||||
if (inputStr.includes('omitempty')) {
|
||||
tags.push('omitempty');
|
||||
}
|
||||
|
||||
if (inputStr.includes('required')) {
|
||||
tags.push('required');
|
||||
}
|
||||
|
||||
if (inputStr === 'int2str') {
|
||||
tags.push('int2str');
|
||||
}
|
||||
|
||||
if (tags.length === 0) {
|
||||
return newtag;
|
||||
}
|
||||
|
||||
return newtag + (newtag.length > 0 ? ',' : '') + tags.join(',');
|
||||
}
|
||||
|
||||
// NOTE: agw is a similar specification in Bytedance,
|
||||
// so we should cover the agw specification here.
|
||||
// some old rules should also be covered here.
|
||||
// eslint-disable-next-line complexity
|
||||
export function extractExtensionConfig(
|
||||
key: string,
|
||||
value: string,
|
||||
ignoreTag = false,
|
||||
) {
|
||||
const config: ExtensionConfig = {};
|
||||
|
||||
// an old rule: go.tag = "json:\"id,omitempty\""
|
||||
if (!ignoreTag && /^go\.tag/.test(key)) {
|
||||
const matches = value.match(goJsonTagRegExp);
|
||||
if (matches) {
|
||||
/* istanbul ignore next */
|
||||
const tagValues = (matches[2] || '').split(',');
|
||||
const tagKey = tagValues[0];
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (tagKey) {
|
||||
if (tagKey === '-') {
|
||||
config.tag = 'ignore';
|
||||
} else if (/^[a-zA-Z0-9_-]+$/.test(tagKey)) {
|
||||
config.key = tagKey;
|
||||
}
|
||||
}
|
||||
|
||||
const extraInfos = tagValues.slice(1).map(item => item.trim());
|
||||
if (extraInfos.includes('string')) {
|
||||
config.value_type = 'string';
|
||||
}
|
||||
|
||||
const newTag = getTag(config.tag, matches[2]);
|
||||
if (newTag) {
|
||||
config.tag = newTag;
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// the agw rules: agw.source = 'header' or agw.target = 'http_code'
|
||||
if (key === 'source' || key === 'target') {
|
||||
/* istanbul ignore else */
|
||||
if (value === 'http_code') {
|
||||
config.position = 'status_code';
|
||||
} else if (POSITIONS.includes(value)) {
|
||||
config.position = value as Position;
|
||||
}
|
||||
} else if (key === 'method') {
|
||||
// the agw rule: agw.method = 'POST|GET'
|
||||
const method = value.split('|')[0];
|
||||
if (UPPERCASE_METHODS.includes(method)) {
|
||||
config.method = method as Method;
|
||||
}
|
||||
} else if (key === 'position') {
|
||||
if (POSITIONS.includes(value)) {
|
||||
config.position = value as Position;
|
||||
}
|
||||
} else if (key === 'serializer') {
|
||||
if (SERIALIZERS.includes(value)) {
|
||||
config.serializer = value as Serializer;
|
||||
}
|
||||
} else if (LOWERCASE_METHODS.includes(key)) {
|
||||
config.method = key.toUpperCase() as Method;
|
||||
config.uri = value;
|
||||
} else if (POSITIONS.includes(key)) {
|
||||
config.position = key as Position;
|
||||
|
||||
// cover an old rule: (api.body = "tags, omitempty")
|
||||
const parts = value.split(',');
|
||||
config.key = parts[0].trim().replace(/\[\]$/, '');
|
||||
const newTag = getTag(config.tag, value);
|
||||
if (newTag) {
|
||||
config.tag = newTag;
|
||||
}
|
||||
} else if (
|
||||
[
|
||||
'uri_prefix',
|
||||
'uri',
|
||||
'group',
|
||||
'custom',
|
||||
'version',
|
||||
'key',
|
||||
'web_type',
|
||||
'value_type',
|
||||
'tag',
|
||||
].includes(key)
|
||||
) {
|
||||
config[key as 'uri_prefix'] = value;
|
||||
} else if (key === 'req.headers') {
|
||||
// NOTE: Compliance with old specifications
|
||||
if (value.includes('x-www-form-urlencoded')) {
|
||||
config.serializer = 'urlencoded';
|
||||
} else if (value.includes('form-data')) {
|
||||
config.serializer = 'form';
|
||||
} else if (value.includes('json')) {
|
||||
config.serializer = 'json';
|
||||
}
|
||||
} else if (key === 'js_conv' && ['string', 'str', 'true'].includes(value)) {
|
||||
// NOTE: used in bytedance
|
||||
const newTag = getTag(config.tag, 'int2str');
|
||||
if (newTag) {
|
||||
config.tag = newTag;
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function filterConfig(
|
||||
config: ExtensionConfig,
|
||||
keys: string[],
|
||||
): ExtensionConfig {
|
||||
const filteredConfig: ExtensionConfig = {};
|
||||
Object.keys(config).forEach(key => {
|
||||
if (keys.includes(key)) {
|
||||
filteredConfig[key as ExtensionConfigStringKey] =
|
||||
config[key as ExtensionConfigStringKey];
|
||||
}
|
||||
});
|
||||
|
||||
return filteredConfig;
|
||||
}
|
||||
|
||||
export function filterFieldExtensionConfig(config: ExtensionConfig) {
|
||||
return filterConfig(
|
||||
config,
|
||||
FIELD_EXTENSTION_CONFIG_KEYS,
|
||||
) as FieldExtensionConfig;
|
||||
}
|
||||
|
||||
export function filterFunctionExtensionConfig(config: ExtensionConfig) {
|
||||
return filterConfig(
|
||||
config,
|
||||
FUNCTION_EXTENSTION_CONFIG_KEYS,
|
||||
) as FunctionExtensionConfig;
|
||||
}
|
||||
|
||||
export function filterServiceExtensionConfig(config: ExtensionConfig) {
|
||||
return filterConfig(
|
||||
config,
|
||||
SERVICE_EXTENSTION_CONFIG_KEYS,
|
||||
) as ServiceExtensionConfig;
|
||||
}
|
||||
17
frontend/infra/idl/idl-parser/src/index.ts
Normal file
17
frontend/infra/idl/idl-parser/src/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './unify';
|
||||
199
frontend/infra/idl/idl-parser/src/proto/index.ts
Normal file
199
frontend/infra/idl/idl-parser/src/proto/index.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint no-param-reassign: ["error", { "props": false }] */
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { parse as protoParse } from 'proto-parser';
|
||||
import { logAndThrowError } from '../utils';
|
||||
import {
|
||||
type ProtoDocument,
|
||||
type ProtoError,
|
||||
SyntaxType,
|
||||
type MessageDefinition,
|
||||
type ServiceDefinition,
|
||||
type MethodDefinition,
|
||||
type NamespaceBase,
|
||||
type NamespaceDefinition,
|
||||
type FieldExtensionConfig,
|
||||
type ServiceExtensionConfig,
|
||||
type FunctionExtensionConfig,
|
||||
} from './type';
|
||||
|
||||
import * as extensionUtil from '../common/extension_util';
|
||||
|
||||
export * from './type';
|
||||
|
||||
// NOTE: cover old Rules: (api_method) = 'POST'
|
||||
const oldRuleRegExp =
|
||||
/\(api_req\)\.|\(api_resp\)\.|\(api_method\)\.|\(pb_idl\.api_method\)\./;
|
||||
|
||||
function extractExtensionConfigFromOption(
|
||||
optionKey: string,
|
||||
optionValue: string,
|
||||
) {
|
||||
let key = '';
|
||||
|
||||
if (/^\(api\.(.*)\)/.test(optionKey)) {
|
||||
key = optionKey.replace(/^\(api\.(.*)\)/, '$1');
|
||||
} else if (oldRuleRegExp.test(optionKey)) {
|
||||
key = optionKey.replace(oldRuleRegExp, '');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
const config = extensionUtil.extractExtensionConfig(key, optionValue);
|
||||
return config;
|
||||
}
|
||||
|
||||
function convertFieldOptions(message: MessageDefinition) {
|
||||
const { name, fields } = message;
|
||||
|
||||
for (const field of Object.values(fields)) {
|
||||
if (field.options && Object.keys(field.options).length > 0) {
|
||||
const extensionConfig: FieldExtensionConfig = {};
|
||||
const fieldName = field.name;
|
||||
const fieldType = field.type;
|
||||
|
||||
for (const key of Object.keys(field.options)) {
|
||||
const value = field.options[key];
|
||||
const config = extractExtensionConfigFromOption(key, value);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (config) {
|
||||
if ('key' in config) {
|
||||
// a key which is the same with the field name will make no sense
|
||||
if (config.key === fieldName) {
|
||||
delete config.key;
|
||||
}
|
||||
} else if (config.position === 'path') {
|
||||
if (
|
||||
['double', 'float', 'bool', 'bytes'].includes(fieldType.value)
|
||||
) {
|
||||
const errorMessage = `the type of path parameter '${fieldName}' in '${name}' should be string or integer`;
|
||||
logAndThrowError(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(extensionConfig, config);
|
||||
}
|
||||
}
|
||||
|
||||
field.extensionConfig =
|
||||
extensionUtil.filterFieldExtensionConfig(extensionConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertMethodOptions(method: MethodDefinition) {
|
||||
if (!(method.options && Object.keys(method.options).length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionConfig: FunctionExtensionConfig = {};
|
||||
|
||||
for (const key of Object.keys(method.options)) {
|
||||
const value = method.options[key];
|
||||
const config = extractExtensionConfigFromOption(key, value);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (config) {
|
||||
Object.assign(extensionConfig, config);
|
||||
}
|
||||
}
|
||||
|
||||
method.extensionConfig =
|
||||
extensionUtil.filterFunctionExtensionConfig(extensionConfig);
|
||||
}
|
||||
|
||||
// NOTE: omit message definitions nested in a service definition
|
||||
function convertServiceOptions(service: ServiceDefinition) {
|
||||
if (service.options && Object.keys(service.options).length > 0) {
|
||||
const extensionConfig: ServiceExtensionConfig = {};
|
||||
|
||||
for (const key of Object.keys(service.options)) {
|
||||
const value = service.options[key];
|
||||
const config = extractExtensionConfigFromOption(key, value);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (config) {
|
||||
Object.assign(extensionConfig, config);
|
||||
}
|
||||
}
|
||||
|
||||
service.extensionConfig =
|
||||
extensionUtil.filterServiceExtensionConfig(extensionConfig);
|
||||
}
|
||||
|
||||
if (Object.keys(service.methods).length > 0) {
|
||||
Object.keys(service.methods).forEach(serviceName => {
|
||||
convertMethodOptions(service.methods[serviceName]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function convertNamespace(namespaceBase: NamespaceBase) {
|
||||
const { nested } = namespaceBase;
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!(nested && Object.keys(nested).length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const name of Object.keys(nested)) {
|
||||
const nestedStatement = nested[name];
|
||||
const { syntaxType } = nestedStatement;
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (syntaxType === SyntaxType.ServiceDefinition) {
|
||||
convertServiceOptions(nestedStatement as ServiceDefinition);
|
||||
} else if (syntaxType === SyntaxType.MessageDefinition) {
|
||||
convertFieldOptions(nestedStatement as MessageDefinition);
|
||||
} else if (syntaxType === SyntaxType.NamespaceDefinition) {
|
||||
convertNamespace(nestedStatement as NamespaceDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function parse(source: string) {
|
||||
let content: string;
|
||||
let filePath = 'source';
|
||||
|
||||
if (/\.proto$/.test(source)) {
|
||||
filePath = path.resolve(process.cwd(), source);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
const message = `no such file: ${filePath}`;
|
||||
logAndThrowError(message);
|
||||
}
|
||||
|
||||
content = fs.readFileSync(filePath, 'utf8');
|
||||
} else {
|
||||
content = source;
|
||||
}
|
||||
|
||||
const document: ProtoDocument | ProtoError = protoParse(content);
|
||||
|
||||
if ((document as ProtoError).syntaxType === SyntaxType.ProtoError) {
|
||||
const { line, message } = document as ProtoError;
|
||||
const fullMessage = `${message}(${filePath}:${line}:0)`;
|
||||
logAndThrowError(fullMessage);
|
||||
}
|
||||
|
||||
convertNamespace((document as ProtoDocument).root);
|
||||
return document as ProtoDocument;
|
||||
}
|
||||
154
frontend/infra/idl/idl-parser/src/proto/type.ts
Normal file
154
frontend/infra/idl/idl-parser/src/proto/type.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
type FieldExtensionConfig,
|
||||
type ServiceExtensionConfig,
|
||||
type FunctionExtensionConfig,
|
||||
} from '../common/extension_type';
|
||||
|
||||
export * from '../common/extension_type';
|
||||
|
||||
export type KeywordType =
|
||||
| 'double'
|
||||
| 'float'
|
||||
| 'int32'
|
||||
| 'int64'
|
||||
| 'uint32'
|
||||
| 'uint64'
|
||||
| 'sint32'
|
||||
| 'sint64'
|
||||
| 'fixed32'
|
||||
| 'fixed64'
|
||||
| 'sfixed32'
|
||||
| 'sfixed64'
|
||||
| 'bool'
|
||||
| 'string'
|
||||
| 'bytes';
|
||||
|
||||
export type FieldRule = 'repeated' | 'required';
|
||||
|
||||
export interface BaseType {
|
||||
syntaxType: SyntaxType.BaseType;
|
||||
value: KeywordType;
|
||||
}
|
||||
|
||||
export interface Identifier {
|
||||
syntaxType: SyntaxType.Identifier;
|
||||
value: string;
|
||||
resolvedValue?: string;
|
||||
}
|
||||
|
||||
export type FieldType = BaseType | Identifier;
|
||||
|
||||
export interface ReflectionObject {
|
||||
name: string;
|
||||
fullName?: string;
|
||||
options?: Record<string, string>;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface FieldDefinition extends ReflectionObject {
|
||||
syntaxType: SyntaxType.FieldDefinition;
|
||||
id: number;
|
||||
type: FieldType;
|
||||
rule?: FieldRule;
|
||||
optional: boolean;
|
||||
required: boolean;
|
||||
repeated: boolean;
|
||||
map: boolean;
|
||||
extend?: string;
|
||||
keyType?: FieldType;
|
||||
extensionConfig?: FieldExtensionConfig;
|
||||
}
|
||||
|
||||
export interface OneofDefinition extends ReflectionObject {
|
||||
syntaxType: SyntaxType.OneofDefinition;
|
||||
oneof: string[];
|
||||
}
|
||||
|
||||
export interface MethodDefinition extends ReflectionObject {
|
||||
syntaxType: SyntaxType.MethodDefinition;
|
||||
requestType: FieldType;
|
||||
responseType: FieldType;
|
||||
extensionConfig?: FunctionExtensionConfig;
|
||||
}
|
||||
|
||||
export interface NamespaceBase extends ReflectionObject {
|
||||
syntaxType: SyntaxType;
|
||||
nested?: Record<string, NamespaceBase>;
|
||||
}
|
||||
|
||||
export interface NamespaceDefinition extends NamespaceBase {
|
||||
syntaxType: SyntaxType.NamespaceDefinition;
|
||||
}
|
||||
|
||||
export interface MessageDefinition extends NamespaceBase {
|
||||
syntaxType: SyntaxType.MessageDefinition;
|
||||
fields: Record<string, FieldDefinition>;
|
||||
oneofs: Record<string, OneofDefinition>;
|
||||
extensions?: string[];
|
||||
reserved?: number[] | string;
|
||||
}
|
||||
|
||||
export interface EnumDefinition extends NamespaceBase {
|
||||
syntaxType: SyntaxType.EnumDefinition;
|
||||
values: Record<string, number>;
|
||||
reserved?: number[] | string;
|
||||
}
|
||||
|
||||
export interface ServiceDefinition extends NamespaceBase {
|
||||
syntaxType: SyntaxType.ServiceDefinition;
|
||||
methods: Record<string, MethodDefinition>;
|
||||
extensionConfig?: ServiceExtensionConfig;
|
||||
}
|
||||
|
||||
export interface ProtoRoot extends NamespaceBase {
|
||||
syntaxType: SyntaxType.ProtoRoot;
|
||||
}
|
||||
|
||||
export interface ProtoDocument {
|
||||
syntaxType: SyntaxType.ProtoDocument;
|
||||
imports?: string[];
|
||||
weakImports?: string[];
|
||||
package?: string;
|
||||
syntax: 'proto2' | 'proto3';
|
||||
root: ProtoRoot;
|
||||
}
|
||||
|
||||
export interface ProtoError {
|
||||
syntaxType: SyntaxType.ProtoError;
|
||||
line: number;
|
||||
message: string;
|
||||
error: Error;
|
||||
}
|
||||
|
||||
export enum SyntaxType {
|
||||
BaseType = 'BaseType',
|
||||
Identifier = 'Identifier',
|
||||
OneofDefinition = 'OneofDefinition',
|
||||
FieldDefinition = 'FieldDefinition',
|
||||
MethodDefinition = 'MethodDefinition',
|
||||
|
||||
NamespaceDefinition = 'NamespaceDefinition',
|
||||
MessageDefinition = 'MessageDefinition',
|
||||
EnumDefinition = 'EnumDefinition',
|
||||
ServiceDefinition = 'ServiceDefinition',
|
||||
|
||||
ProtoRoot = 'ProtoRoot',
|
||||
ProtoDocument = 'ProtoDocument',
|
||||
ProtoError = 'ProtoError',
|
||||
}
|
||||
288
frontend/infra/idl/idl-parser/src/thrift/index.ts
Normal file
288
frontend/infra/idl/idl-parser/src/thrift/index.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint no-param-reassign: ["error", { "props": false }] */
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { parse as thriftParse } from '@lancewuz/thrift-parser';
|
||||
import { logAndThrowError } from '../utils';
|
||||
import {
|
||||
type ThriftDocument,
|
||||
type ThriftErrors,
|
||||
SyntaxType,
|
||||
type InterfaceWithFields,
|
||||
type Annotation,
|
||||
type ServiceDefinition,
|
||||
type FunctionDefinition,
|
||||
type EnumDefinition,
|
||||
type FieldExtensionConfig,
|
||||
type ServiceExtensionConfig,
|
||||
type FunctionExtensionConfig,
|
||||
type Comment,
|
||||
} from './type';
|
||||
import * as extensionUtil from '../common/extension_util';
|
||||
|
||||
// export statements
|
||||
export * from './type';
|
||||
|
||||
// global variables
|
||||
let reviseTailComment = true;
|
||||
|
||||
function extractExtensionConfigFromAnnotation(annotation: Annotation) {
|
||||
let key = annotation.name.value;
|
||||
const value = (annotation.value && annotation.value.value) || '';
|
||||
|
||||
if (/^((agw\.)|(api\.)|(go\.tag))/.test(key) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (/^((agw\.)|(api\.))/.test(key)) {
|
||||
key = key.slice(4);
|
||||
}
|
||||
|
||||
const config = extensionUtil.extractExtensionConfig(key, value);
|
||||
return config;
|
||||
}
|
||||
|
||||
function convertFieldAnnotations(struct: InterfaceWithFields) {
|
||||
const name = struct.name.value;
|
||||
const { fields } = struct;
|
||||
|
||||
for (const field of fields) {
|
||||
if (field.annotations && field.annotations.annotations.length > 0) {
|
||||
const extensionConfig: FieldExtensionConfig = {};
|
||||
const fieldName = field.name.value;
|
||||
const fieldSyntaxType = field.fieldType.type;
|
||||
|
||||
for (const annotation of field.annotations.annotations) {
|
||||
const config = extractExtensionConfigFromAnnotation(annotation);
|
||||
if (config) {
|
||||
if ('key' in config) {
|
||||
// a key which is the same with the field name will make no sense
|
||||
if (config.key === fieldName) {
|
||||
delete config.key;
|
||||
}
|
||||
} else if (config.position === 'path') {
|
||||
if (
|
||||
[
|
||||
SyntaxType.DoubleKeyword,
|
||||
SyntaxType.BoolKeyword,
|
||||
SyntaxType.ByteKeyword,
|
||||
SyntaxType.ListKeyword,
|
||||
SyntaxType.SetKeyword,
|
||||
SyntaxType.MapKeyword,
|
||||
SyntaxType.VoidKeyword,
|
||||
].includes(fieldSyntaxType)
|
||||
) {
|
||||
const message = `the type of path parameter '${fieldName}' in '${name}' should be string or integer`;
|
||||
logAndThrowError(message);
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(extensionConfig, config);
|
||||
}
|
||||
}
|
||||
|
||||
field.extensionConfig =
|
||||
extensionUtil.filterFieldExtensionConfig(extensionConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertFunctionAnnotations(func: FunctionDefinition) {
|
||||
const { annotations } = func;
|
||||
|
||||
if (!(annotations && annotations.annotations.length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionConfig: FunctionExtensionConfig = {};
|
||||
|
||||
for (const annotation of annotations.annotations) {
|
||||
const config = extractExtensionConfigFromAnnotation(annotation);
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (config) {
|
||||
Object.assign(extensionConfig, config);
|
||||
}
|
||||
}
|
||||
|
||||
func.extensionConfig =
|
||||
extensionUtil.filterFunctionExtensionConfig(extensionConfig);
|
||||
}
|
||||
|
||||
function convertServiceAnnotations(service: ServiceDefinition) {
|
||||
const { annotations } = service;
|
||||
|
||||
if (annotations && annotations.annotations.length > 0) {
|
||||
const extensionConfig: ServiceExtensionConfig = {};
|
||||
|
||||
for (const annotation of annotations.annotations) {
|
||||
const config = extractExtensionConfigFromAnnotation(annotation);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (config) {
|
||||
Object.assign(extensionConfig, config);
|
||||
}
|
||||
}
|
||||
|
||||
service.extensionConfig =
|
||||
extensionUtil.filterServiceExtensionConfig(extensionConfig);
|
||||
}
|
||||
|
||||
if (service.functions.length > 0) {
|
||||
service.functions.forEach(func => {
|
||||
convertFunctionAnnotations(func);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function reviseFieldComments(struct: InterfaceWithFields) {
|
||||
const { fields } = struct;
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (fields.length < 2) {return;}
|
||||
|
||||
for (let i = fields.length - 1; i > 0; i--) {
|
||||
const currentField = fields[i];
|
||||
const prevField = fields[i - 1];
|
||||
const prevFieldEndLine = prevField.loc.end.line;
|
||||
const prevFieldComments = prevField.comments;
|
||||
|
||||
for (let j = 0; j < prevFieldComments.length; j++) {
|
||||
if (prevFieldComments[j].loc.end.line > prevFieldEndLine) {
|
||||
const dislocatedComments = prevFieldComments.splice(
|
||||
j,
|
||||
prevFieldComments.length - j,
|
||||
);
|
||||
currentField.comments = [
|
||||
...dislocatedComments,
|
||||
...currentField.comments,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reviseEnumMemberComments(enm: EnumDefinition) {
|
||||
const { members } = enm;
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (members.length < 2) {return;}
|
||||
|
||||
for (let i = 0; i < members.length - 1; i++) {
|
||||
const currentMember = members[i];
|
||||
const nextMember = members[i + 1];
|
||||
const currentMemberEndLine = currentMember.loc.end.line;
|
||||
const nextMemberFirstComment = nextMember.comments[0];
|
||||
|
||||
if (
|
||||
nextMemberFirstComment &&
|
||||
nextMemberFirstComment.loc.end.line === currentMemberEndLine
|
||||
) {
|
||||
const dislocatedComment = nextMember.comments.shift() as Comment;
|
||||
currentMember.comments.push(dislocatedComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reviseFunctionComments(service: ServiceDefinition) {
|
||||
const { functions } = service;
|
||||
if (functions.length < 2) {return;}
|
||||
|
||||
for (let i = 0; i < functions.length - 1; i++) {
|
||||
const currentFunction = functions[i];
|
||||
const nextFunction = functions[i + 1];
|
||||
const currentFunctionEndLine = currentFunction.loc.end.line;
|
||||
const nextFunctionFirstComment = nextFunction.comments[0];
|
||||
|
||||
if (
|
||||
nextFunctionFirstComment &&
|
||||
nextFunctionFirstComment.loc.end.line === currentFunctionEndLine
|
||||
) {
|
||||
const dislocatedComment = nextFunction.comments.shift() as Comment;
|
||||
currentFunction.comments.push(dislocatedComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ParseOption {
|
||||
reviseTailComment?: boolean;
|
||||
}
|
||||
|
||||
const defualtParseOption = {
|
||||
reviseTailComment: true,
|
||||
};
|
||||
|
||||
export function parse(source: string, option?: ParseOption): ThriftDocument {
|
||||
let content: string;
|
||||
let filePath = 'source';
|
||||
|
||||
if (/\.thrift$/.test(source)) {
|
||||
filePath = path.resolve(process.cwd(), source);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
const message = `no such file: ${filePath}`;
|
||||
logAndThrowError(message);
|
||||
}
|
||||
|
||||
content = fs.readFileSync(filePath, 'utf8');
|
||||
} else {
|
||||
content = source;
|
||||
}
|
||||
|
||||
const document: ThriftDocument | ThriftErrors = thriftParse(content);
|
||||
|
||||
if ((document as ThriftErrors).type === SyntaxType.ThriftErrors) {
|
||||
const error = (document as ThriftErrors).errors[0];
|
||||
const { start } = error.loc;
|
||||
const message = `${error.message}(${filePath}:${start.line}:${start.column})`;
|
||||
logAndThrowError(message);
|
||||
}
|
||||
|
||||
const parseOption = { ...defualtParseOption, ...option };
|
||||
reviseTailComment = parseOption.reviseTailComment;
|
||||
|
||||
for (const statement of (document as ThriftDocument).body) {
|
||||
/* istanbul ignore else */
|
||||
if (
|
||||
[SyntaxType.StructDefinition, SyntaxType.UnionDefinition].includes(
|
||||
statement.type,
|
||||
)
|
||||
) {
|
||||
convertFieldAnnotations(statement as InterfaceWithFields);
|
||||
|
||||
if (reviseTailComment) {
|
||||
reviseFieldComments(statement as InterfaceWithFields);
|
||||
}
|
||||
} else if (statement.type === SyntaxType.ServiceDefinition) {
|
||||
convertServiceAnnotations(statement);
|
||||
|
||||
if (reviseTailComment) {
|
||||
reviseFunctionComments(statement);
|
||||
}
|
||||
} else if (statement.type === SyntaxType.EnumDefinition) {
|
||||
/* istanbul ignore else */
|
||||
if (reviseTailComment) {
|
||||
reviseEnumMemberComments(statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return document as ThriftDocument;
|
||||
}
|
||||
460
frontend/infra/idl/idl-parser/src/thrift/type.ts
Normal file
460
frontend/infra/idl/idl-parser/src/thrift/type.ts
Normal file
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
type FieldExtensionConfig,
|
||||
type ServiceExtensionConfig,
|
||||
type FunctionExtensionConfig,
|
||||
} from '../common/extension_type';
|
||||
|
||||
export * from '../common/extension_type';
|
||||
|
||||
export interface ThriftError {
|
||||
type: ErrorType;
|
||||
message: string;
|
||||
loc: TextLocation;
|
||||
}
|
||||
|
||||
export interface ParseError extends ThriftError {
|
||||
type: ErrorType.ParseError;
|
||||
}
|
||||
|
||||
export interface ScanError extends ThriftError {
|
||||
type: ErrorType.ScanError;
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
type: SyntaxType;
|
||||
}
|
||||
|
||||
export interface SyntaxNode extends Node {
|
||||
loc: TextLocation;
|
||||
}
|
||||
|
||||
export interface StructLike {
|
||||
name: Identifier;
|
||||
fields: Array<FieldDefinition>;
|
||||
annotations?: Annotations;
|
||||
comments: Array<Comment>;
|
||||
loc: TextLocation;
|
||||
}
|
||||
|
||||
export interface TextLocation {
|
||||
start: TextPosition;
|
||||
end: TextPosition;
|
||||
}
|
||||
|
||||
export interface TextPosition {
|
||||
line: number;
|
||||
column: number;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface Token extends SyntaxNode {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface ThriftDocument extends Node {
|
||||
type: SyntaxType.ThriftDocument;
|
||||
body: Array<ThriftStatement>;
|
||||
tokens?: Array<Token>;
|
||||
}
|
||||
|
||||
export interface ThriftErrors {
|
||||
type: SyntaxType.ThriftErrors;
|
||||
errors: Array<ThriftError>;
|
||||
}
|
||||
|
||||
export type ThriftStatement =
|
||||
| NamespaceDefinition
|
||||
| IncludeDefinition
|
||||
| CppIncludeDefinition
|
||||
| ConstDefinition
|
||||
| StructDefinition
|
||||
| EnumDefinition
|
||||
| ExceptionDefinition
|
||||
| UnionDefinition
|
||||
| TypedefDefinition
|
||||
| ServiceDefinition;
|
||||
|
||||
export type CommentType = SyntaxType.CommentLine | SyntaxType.CommentBlock;
|
||||
|
||||
export type Comment = CommentLine | CommentBlock;
|
||||
|
||||
export interface CommentLine extends SyntaxNode {
|
||||
type: SyntaxType.CommentLine;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface CommentBlock extends SyntaxNode {
|
||||
type: SyntaxType.CommentBlock;
|
||||
value: Array<string>;
|
||||
}
|
||||
|
||||
export interface Annotations extends SyntaxNode {
|
||||
annotations: Array<Annotation>;
|
||||
}
|
||||
|
||||
export interface Annotation extends SyntaxNode {
|
||||
name: Identifier;
|
||||
value?: StringLiteral;
|
||||
}
|
||||
|
||||
export interface PrimarySyntax extends SyntaxNode {
|
||||
comments: Array<Comment>;
|
||||
}
|
||||
|
||||
export type FieldType = BaseType | ContainerType | Identifier;
|
||||
|
||||
export type FunctionType = FieldType | VoidType;
|
||||
|
||||
export type KeywordType =
|
||||
| SyntaxType.StringKeyword
|
||||
| SyntaxType.DoubleKeyword
|
||||
| SyntaxType.BoolKeyword
|
||||
| SyntaxType.I8Keyword
|
||||
| SyntaxType.I16Keyword
|
||||
| SyntaxType.I32Keyword
|
||||
| SyntaxType.I64Keyword
|
||||
| SyntaxType.BinaryKeyword
|
||||
| SyntaxType.ByteKeyword;
|
||||
|
||||
export interface VoidType extends SyntaxNode {
|
||||
type: SyntaxType.VoidKeyword;
|
||||
}
|
||||
|
||||
export type ContainerType = SetType | MapType | ListType;
|
||||
|
||||
export interface BaseType extends SyntaxNode {
|
||||
type: KeywordType;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface SetType extends SyntaxNode {
|
||||
type: SyntaxType.SetType;
|
||||
valueType: FieldType;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface ListType extends SyntaxNode {
|
||||
type: SyntaxType.ListType;
|
||||
valueType: FieldType;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface MapType extends SyntaxNode {
|
||||
type: SyntaxType.MapType;
|
||||
keyType: FieldType;
|
||||
valueType: FieldType;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export type ConstValue =
|
||||
| StringLiteral
|
||||
| IntConstant
|
||||
| DoubleConstant
|
||||
| BooleanLiteral
|
||||
| ConstMap
|
||||
| ConstList
|
||||
| Identifier;
|
||||
|
||||
export interface NamespaceDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.NamespaceDefinition;
|
||||
scope: Identifier;
|
||||
name: Identifier;
|
||||
}
|
||||
|
||||
export interface ConstDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.ConstDefinition;
|
||||
name: Identifier;
|
||||
fieldType: FieldType;
|
||||
initializer: ConstValue;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export type FieldRequired = 'required' | 'optional';
|
||||
|
||||
export interface IncludeDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.IncludeDefinition;
|
||||
path: StringLiteral;
|
||||
}
|
||||
|
||||
export interface CppIncludeDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.CppIncludeDefinition;
|
||||
path: StringLiteral;
|
||||
}
|
||||
|
||||
export interface InterfaceWithFields extends PrimarySyntax {
|
||||
name: Identifier;
|
||||
fields: Array<FieldDefinition>;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface StructDefinition extends InterfaceWithFields {
|
||||
type: SyntaxType.StructDefinition;
|
||||
}
|
||||
|
||||
export interface UnionDefinition extends InterfaceWithFields {
|
||||
type: SyntaxType.UnionDefinition;
|
||||
}
|
||||
|
||||
export interface ExceptionDefinition extends InterfaceWithFields {
|
||||
type: SyntaxType.ExceptionDefinition;
|
||||
}
|
||||
|
||||
export interface FieldDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.FieldDefinition;
|
||||
name: Identifier;
|
||||
fieldID: FieldID | null;
|
||||
fieldType: FunctionType;
|
||||
requiredness: FieldRequired | null;
|
||||
defaultValue: ConstValue | null;
|
||||
annotations?: Annotations;
|
||||
extensionConfig?: FieldExtensionConfig;
|
||||
}
|
||||
|
||||
export interface FieldID extends SyntaxNode {
|
||||
type: SyntaxType.FieldID;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface EnumDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.EnumDefinition;
|
||||
name: Identifier;
|
||||
members: Array<EnumMember>;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface EnumMember extends PrimarySyntax {
|
||||
type: SyntaxType.EnumMember;
|
||||
name: Identifier;
|
||||
initializer: IntConstant | null;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface TypedefDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.TypedefDefinition;
|
||||
name: Identifier;
|
||||
definitionType: FieldType;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface ServiceDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.ServiceDefinition;
|
||||
name: Identifier;
|
||||
extends: Identifier | null;
|
||||
functions: Array<FunctionDefinition>;
|
||||
annotations?: Annotations;
|
||||
extensionConfig?: ServiceExtensionConfig;
|
||||
}
|
||||
|
||||
export interface FunctionDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.FunctionDefinition;
|
||||
name: Identifier;
|
||||
oneway: boolean;
|
||||
returnType: FunctionType;
|
||||
fields: Array<FieldDefinition>;
|
||||
throws: Array<FieldDefinition>;
|
||||
modifiers: Array<Token>;
|
||||
annotations?: Annotations;
|
||||
extensionConfig?: FunctionExtensionConfig;
|
||||
}
|
||||
|
||||
export interface ParametersDefinition extends SyntaxNode {
|
||||
type: SyntaxType.ParametersDefinition;
|
||||
fields: Array<FieldDefinition>;
|
||||
}
|
||||
|
||||
export interface ThrowsDefinition extends SyntaxNode {
|
||||
type: SyntaxType.ThrowsDefinition;
|
||||
fields: Array<FieldDefinition>;
|
||||
}
|
||||
|
||||
export interface StringLiteral extends SyntaxNode {
|
||||
type: SyntaxType.StringLiteral;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface BooleanLiteral extends SyntaxNode {
|
||||
type: SyntaxType.BooleanLiteral;
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
export interface IntegerLiteral extends SyntaxNode {
|
||||
type: SyntaxType.IntegerLiteral;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface HexLiteral extends SyntaxNode {
|
||||
type: SyntaxType.HexLiteral;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface FloatLiteral extends SyntaxNode {
|
||||
type: SyntaxType.FloatLiteral;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ExponentialLiteral extends SyntaxNode {
|
||||
type: SyntaxType.ExponentialLiteral;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IntConstant extends SyntaxNode {
|
||||
type: SyntaxType.IntConstant;
|
||||
value: IntegerLiteral | HexLiteral;
|
||||
}
|
||||
|
||||
export interface DoubleConstant extends SyntaxNode {
|
||||
type: SyntaxType.DoubleConstant;
|
||||
value: FloatLiteral | ExponentialLiteral;
|
||||
}
|
||||
|
||||
export interface ConstMap extends SyntaxNode {
|
||||
type: SyntaxType.ConstMap;
|
||||
properties: Array<PropertyAssignment>;
|
||||
}
|
||||
|
||||
export interface ConstList extends SyntaxNode {
|
||||
type: SyntaxType.ConstList;
|
||||
elements: Array<ConstValue>;
|
||||
}
|
||||
|
||||
export interface PropertyAssignment extends SyntaxNode {
|
||||
type: SyntaxType.PropertyAssignment;
|
||||
name: ConstValue;
|
||||
initializer: ConstValue;
|
||||
}
|
||||
|
||||
export interface Identifier extends SyntaxNode {
|
||||
type: SyntaxType.Identifier;
|
||||
value: string;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export enum ErrorType {
|
||||
ParseError = 'ParseError',
|
||||
ScanError = 'ScanError',
|
||||
}
|
||||
|
||||
export enum SyntaxType {
|
||||
ThriftDocument = 'ThriftDocument',
|
||||
ThriftErrors = 'ThriftErrors',
|
||||
|
||||
Identifier = 'Identifier',
|
||||
FieldID = 'FieldID',
|
||||
|
||||
// Statements
|
||||
NamespaceDefinition = 'NamespaceDefinition',
|
||||
IncludeDefinition = 'IncludeDefinition',
|
||||
CppIncludeDefinition = 'CppIncludeDefinition',
|
||||
ConstDefinition = 'ConstDefinition',
|
||||
StructDefinition = 'StructDefinition',
|
||||
EnumDefinition = 'EnumDefinition',
|
||||
ServiceDefinition = 'ServiceDefinition',
|
||||
ExceptionDefinition = 'ExceptionDefinition',
|
||||
TypedefDefinition = 'TypedefDefinition',
|
||||
UnionDefinition = 'UnionDefinition',
|
||||
|
||||
// Fields
|
||||
FieldDefinition = 'FieldDefinition',
|
||||
FunctionDefinition = 'FunctionDefinition',
|
||||
ParametersDefinition = 'ParametersDefinition',
|
||||
ThrowsDefinition = 'ThrowsDefinition',
|
||||
|
||||
// Type Annotations
|
||||
FieldType = 'FieldType',
|
||||
BaseType = 'BaseType',
|
||||
SetType = 'SetType',
|
||||
MapType = 'MapType',
|
||||
ListType = 'ListType',
|
||||
|
||||
// Values
|
||||
ConstValue = 'ConstValue',
|
||||
IntConstant = 'IntConstant',
|
||||
DoubleConstant = 'DoubleConstant',
|
||||
|
||||
ConstList = 'ConstList',
|
||||
ConstMap = 'ConstMap',
|
||||
EnumMember = 'EnumMember',
|
||||
|
||||
// Literals
|
||||
CommentLine = 'CommentLine',
|
||||
CommentBlock = 'CommentBlock',
|
||||
StringLiteral = 'StringLiteral',
|
||||
IntegerLiteral = 'IntegerLiteral',
|
||||
FloatLiteral = 'FloatLiteral',
|
||||
HexLiteral = 'HexLiteral',
|
||||
ExponentialLiteral = 'ExponentialLiteral',
|
||||
BooleanLiteral = 'BooleanLiteral',
|
||||
PropertyAssignment = 'PropertyAssignment',
|
||||
|
||||
// Tokens
|
||||
LeftParenToken = 'LeftParenToken',
|
||||
RightParenToken = 'RightParenToken',
|
||||
LeftBraceToken = 'LeftBraceToken',
|
||||
RightBraceToken = 'RightBraceToken',
|
||||
LeftBracketToken = 'LeftBracketToken',
|
||||
RightBracketToken = 'RightBracketToken',
|
||||
CommaToken = 'CommaToken',
|
||||
DotToken = 'DotToken',
|
||||
MinusToken = 'MinusToken',
|
||||
SemicolonToken = 'SemicolonToken',
|
||||
ColonToken = 'ColonToken',
|
||||
StarToken = 'StarToken',
|
||||
EqualToken = 'EqualToken',
|
||||
LessThanToken = 'LessThanToken',
|
||||
GreaterThanToken = 'GreaterThanToken',
|
||||
|
||||
// Keywords
|
||||
NamespaceKeyword = 'NamespaceKeyword',
|
||||
IncludeKeyword = 'IncludeKeyword',
|
||||
CppIncludeKeyword = 'CppIncludeKeyword',
|
||||
ExceptionKeyword = 'ExceptionKeyword',
|
||||
ServiceKeyword = 'ServiceKeyword',
|
||||
ExtendsKeyword = 'ExtendsKeyword',
|
||||
RequiredKeyword = 'RequiredKeyword',
|
||||
OptionalKeyword = 'OptionalKeyword',
|
||||
FalseKeyword = 'FalseKeyword',
|
||||
TrueKeyword = 'TrueKeyword',
|
||||
ConstKeyword = 'ConstKeyword',
|
||||
DoubleKeyword = 'DoubleKeyword',
|
||||
StructKeyword = 'StructKeyword',
|
||||
TypedefKeyword = 'TypedefKeyword',
|
||||
UnionKeyword = 'UnionKeyword',
|
||||
StringKeyword = 'StringKeyword',
|
||||
BinaryKeyword = 'BinaryKeyword',
|
||||
BoolKeyword = 'BoolKeyword',
|
||||
ByteKeyword = 'ByteKeyword',
|
||||
EnumKeyword = 'EnumKeyword',
|
||||
SenumKeyword = 'SenumKeyword',
|
||||
ListKeyword = 'ListKeyword',
|
||||
SetKeyword = 'SetKeyword',
|
||||
MapKeyword = 'MapKeyword',
|
||||
I8Keyword = 'I8Keyword',
|
||||
I16Keyword = 'I16Keyword',
|
||||
I32Keyword = 'I32Keyword',
|
||||
I64Keyword = 'I64Keyword',
|
||||
ThrowsKeyword = 'ThrowsKeyword',
|
||||
VoidKeyword = 'VoidKeyword',
|
||||
OnewayKeyword = 'OnewayKeyword',
|
||||
|
||||
// Other
|
||||
Annotation = 'Annotation',
|
||||
Annotations = 'Annotations',
|
||||
|
||||
EOF = 'EOF',
|
||||
}
|
||||
144
frontend/infra/idl/idl-parser/src/unify/index.ts
Normal file
144
frontend/infra/idl/idl-parser/src/unify/index.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { logAndThrowError , mergeObject, getPosixPath } from '../utils';
|
||||
import { parseThriftContent } from './thrift';
|
||||
import { parseProtoContent } from './proto';
|
||||
import { type UnifyDocument } from './type';
|
||||
|
||||
type FileType = 'thrift' | 'proto';
|
||||
|
||||
// export statements
|
||||
export * from './type';
|
||||
|
||||
export interface ParseOption {
|
||||
root?: string;
|
||||
namespaceRefer?: boolean;
|
||||
cache?: boolean;
|
||||
ignoreGoTag?: boolean;
|
||||
ignoreGoTagDash?: boolean;
|
||||
preproccess?: (param: { content: string; path?: string }) => string;
|
||||
searchPaths?: string[];
|
||||
}
|
||||
|
||||
const parseOptionDefault: ParseOption = {
|
||||
root: '.',
|
||||
namespaceRefer: true,
|
||||
cache: false,
|
||||
ignoreGoTag: false,
|
||||
ignoreGoTagDash: false,
|
||||
searchPaths: [],
|
||||
};
|
||||
|
||||
// the key of fileContentMap should be absolute path
|
||||
export function parse(
|
||||
filePath: string,
|
||||
option: ParseOption = {},
|
||||
fileContentMap?: Record<string, string>,
|
||||
): UnifyDocument {
|
||||
const {
|
||||
root,
|
||||
namespaceRefer,
|
||||
cache,
|
||||
ignoreGoTag,
|
||||
ignoreGoTagDash,
|
||||
preproccess,
|
||||
searchPaths,
|
||||
} = mergeObject(parseOptionDefault, option) as Required<ParseOption>;
|
||||
|
||||
const fullRootDir = getPosixPath(path.resolve(process.cwd(), root));
|
||||
let fullFilePath = getPosixPath(path.resolve(fullRootDir, filePath));
|
||||
let idlFileType: FileType = 'thrift';
|
||||
let content = '';
|
||||
|
||||
if (/\.thrift$/.test(filePath)) {
|
||||
fullFilePath = getPosixPath(path.resolve(fullRootDir, filePath));
|
||||
if (fileContentMap) {
|
||||
content = fileContentMap[filePath];
|
||||
if (typeof content === 'undefined') {
|
||||
logAndThrowError(`file "${filePath}" does not exist in fileContentMap`);
|
||||
}
|
||||
} else {
|
||||
if (!fs.existsSync(fullFilePath)) {
|
||||
const message = `no such file: ${fullFilePath}`;
|
||||
logAndThrowError(message);
|
||||
}
|
||||
|
||||
content = fs.readFileSync(fullFilePath, 'utf8');
|
||||
}
|
||||
} else if (/\.proto$/.test(filePath)) {
|
||||
idlFileType = 'proto';
|
||||
fullFilePath = getPosixPath(path.resolve(fullRootDir, filePath));
|
||||
if (fileContentMap) {
|
||||
content = fileContentMap[filePath];
|
||||
if (typeof content === 'undefined') {
|
||||
logAndThrowError(`file "${filePath}" does not exist in fileContentMap`);
|
||||
}
|
||||
} else {
|
||||
if (!fs.existsSync(fullFilePath)) {
|
||||
const message = `no such file: ${fullFilePath}`;
|
||||
logAndThrowError(message);
|
||||
}
|
||||
|
||||
content = fs.readFileSync(fullFilePath, 'utf8');
|
||||
}
|
||||
} else {
|
||||
const message = `invalid filePath: "${filePath}"`;
|
||||
logAndThrowError(message);
|
||||
}
|
||||
|
||||
const absoluteFilePath = getPosixPath(
|
||||
path.relative(fullRootDir, fullFilePath),
|
||||
);
|
||||
if (typeof preproccess === 'function') {
|
||||
content = preproccess({ content, path: absoluteFilePath });
|
||||
}
|
||||
|
||||
if (idlFileType === 'thrift') {
|
||||
const looseAbsoluteFilePath = absoluteFilePath.replace(/\.thrift$/, '');
|
||||
const document = parseThriftContent(
|
||||
content,
|
||||
{
|
||||
loosePath: looseAbsoluteFilePath,
|
||||
rootDir: fullRootDir,
|
||||
namespaceRefer,
|
||||
cache,
|
||||
ignoreGoTag,
|
||||
ignoreGoTagDash,
|
||||
searchPaths,
|
||||
},
|
||||
fileContentMap,
|
||||
);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
const looseAbsoluteFilePath = absoluteFilePath.replace(/\.proto$/, '');
|
||||
const document = parseProtoContent(
|
||||
content,
|
||||
{
|
||||
loosePath: looseAbsoluteFilePath,
|
||||
rootDir: fullRootDir,
|
||||
cache,
|
||||
searchPaths,
|
||||
},
|
||||
fileContentMap,
|
||||
);
|
||||
|
||||
return document;
|
||||
}
|
||||
1014
frontend/infra/idl/idl-parser/src/unify/proto.ts
Normal file
1014
frontend/infra/idl/idl-parser/src/unify/proto.ts
Normal file
File diff suppressed because it is too large
Load Diff
712
frontend/infra/idl/idl-parser/src/unify/thrift.ts
Normal file
712
frontend/infra/idl/idl-parser/src/unify/thrift.ts
Normal file
@@ -0,0 +1,712 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint no-param-reassign: ["error", { "props": false }], import/prefer-default-export: off */
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import * as t from '@lancewuz/thrift-parser';
|
||||
|
||||
import { logAndThrowError, getPosixPath } from '../utils';
|
||||
import * as extensionUtil from '../common/extension_util';
|
||||
import { convertIntToString } from './util';
|
||||
import {
|
||||
SyntaxType,
|
||||
type Annotation,
|
||||
type ServiceDefinition,
|
||||
type FunctionDefinition,
|
||||
type EnumDefinition,
|
||||
type ExtensionConfig,
|
||||
type FieldExtensionConfig,
|
||||
type ServiceExtensionConfig,
|
||||
type FunctionExtensionConfig,
|
||||
type Comment,
|
||||
type Identifier,
|
||||
type ContainerType,
|
||||
type MapType,
|
||||
type StructDefinition,
|
||||
type TypedefDefinition,
|
||||
type TextLocation,
|
||||
type FunctionType,
|
||||
type FieldType,
|
||||
type UnifyStatement,
|
||||
type Annotations,
|
||||
type EnumMember,
|
||||
type UnifyDocument,
|
||||
type FieldDefinition,
|
||||
type ConstDefinition,
|
||||
type ConstValue,
|
||||
} from './type';
|
||||
|
||||
// cache parsed document
|
||||
const fileDocumentMap: Record<string, t.ThriftDocument> = {};
|
||||
const namespaceDefault = 'root';
|
||||
let enumNames: string[] = [];
|
||||
let cache = true;
|
||||
let absoluteFileContentMap: Record<string, string> | undefined;
|
||||
let rootDir = '';
|
||||
let entryLooseAbsoluteFilePath = '';
|
||||
let ignoreGoTag = false;
|
||||
let ignoreGoTagDash = false;
|
||||
let addNamespaceValue: ((fieldType: FunctionType) => void) | undefined;
|
||||
let searchPaths: string[] = [];
|
||||
|
||||
const mergeConfig = (
|
||||
config: Partial<ExtensionConfig>,
|
||||
config2: Partial<ExtensionConfig>,
|
||||
): ExtensionConfig => {
|
||||
const mergedTags: string[] = [];
|
||||
if (config?.tag) {
|
||||
mergedTags.push(config.tag);
|
||||
}
|
||||
if (config2?.tag) {
|
||||
mergedTags.push(config2.tag);
|
||||
}
|
||||
const res = Object.assign(config, config2);
|
||||
if (mergedTags.length > 0) {
|
||||
res.tag = mergedTags.join(',');
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
function extractExtensionConfigFromAnnotation(
|
||||
annotation: Annotation,
|
||||
): false | ExtensionConfig {
|
||||
let key = annotation.name.value;
|
||||
const value = (annotation.value && annotation.value.value) || '';
|
||||
|
||||
if (/^((agw\.)|(api\.)|(go\.tag))/.test(key) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (/^((agw\.)|(api\.))/.test(key)) {
|
||||
key = key.slice(4);
|
||||
}
|
||||
|
||||
const config = extensionUtil.extractExtensionConfig(key, value, ignoreGoTag);
|
||||
return config;
|
||||
}
|
||||
|
||||
function getFieldExtensionConfig(
|
||||
fieldName: string,
|
||||
fieldType: FunctionType,
|
||||
annotations?: Annotations,
|
||||
): FieldExtensionConfig {
|
||||
if (!annotations) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const extensionConfig: FieldExtensionConfig = {};
|
||||
for (const annotation of annotations.annotations) {
|
||||
const config = extractExtensionConfigFromAnnotation(annotation);
|
||||
if (config) {
|
||||
if ('key' in config) {
|
||||
// a key which is the same with the field name will make no sense
|
||||
if (config.key === fieldName) {
|
||||
delete config.key;
|
||||
}
|
||||
} else if (config.position === 'path') {
|
||||
if (
|
||||
[
|
||||
SyntaxType.DoubleKeyword,
|
||||
SyntaxType.BoolKeyword,
|
||||
SyntaxType.ByteKeyword,
|
||||
SyntaxType.ListKeyword,
|
||||
SyntaxType.SetKeyword,
|
||||
SyntaxType.MapKeyword,
|
||||
SyntaxType.VoidKeyword,
|
||||
].includes(fieldType.type)
|
||||
) {
|
||||
const fullFilePath = path.resolve(
|
||||
rootDir,
|
||||
`${entryLooseAbsoluteFilePath}.proto`,
|
||||
);
|
||||
const message = `path parameter '${fieldName}' should be string or integer`;
|
||||
const fullMessage = `${message} (${fullFilePath})`;
|
||||
logAndThrowError(fullMessage, message);
|
||||
}
|
||||
}
|
||||
|
||||
mergeConfig(extensionConfig, config);
|
||||
}
|
||||
}
|
||||
|
||||
const res = extensionUtil.filterFieldExtensionConfig(extensionConfig);
|
||||
return res;
|
||||
}
|
||||
|
||||
function getFuncExtensionConfig(
|
||||
annotations?: Annotations,
|
||||
): FunctionExtensionConfig {
|
||||
if (!annotations) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const extensionConfig: FunctionExtensionConfig = {};
|
||||
for (const annotation of annotations.annotations) {
|
||||
const config = extractExtensionConfigFromAnnotation(annotation);
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (config) {
|
||||
Object.assign(extensionConfig, config);
|
||||
}
|
||||
}
|
||||
|
||||
return extensionUtil.filterFunctionExtensionConfig(extensionConfig);
|
||||
}
|
||||
|
||||
function getServiceExtensionConfig(
|
||||
annotations?: Annotations,
|
||||
): ServiceExtensionConfig {
|
||||
if (!annotations) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const extensionConfig: ServiceExtensionConfig = {};
|
||||
for (const annotation of annotations.annotations) {
|
||||
const config = extractExtensionConfigFromAnnotation(annotation);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (config) {
|
||||
Object.assign(extensionConfig, config);
|
||||
}
|
||||
}
|
||||
|
||||
return extensionUtil.filterServiceExtensionConfig(extensionConfig);
|
||||
}
|
||||
|
||||
function reviseFieldComments(fields: (FieldDefinition | EnumMember)[]): void {
|
||||
/* istanbul ignore next */
|
||||
if (fields.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// move previous comments to current field
|
||||
for (let i = fields.length - 1; i > 0; i--) {
|
||||
const currentField = fields[i];
|
||||
const prevField = fields[i - 1];
|
||||
const prevFieldEndLine = (prevField.loc as TextLocation).end.line;
|
||||
const prevFieldComments = prevField.comments;
|
||||
|
||||
for (let j = 0; j < prevFieldComments.length; j++) {
|
||||
if (
|
||||
(prevFieldComments[j].loc as TextLocation).end.line > prevFieldEndLine
|
||||
) {
|
||||
const dislocatedComments = prevFieldComments.splice(
|
||||
j,
|
||||
prevFieldComments.length - j,
|
||||
);
|
||||
currentField.comments = [
|
||||
...dislocatedComments,
|
||||
...currentField.comments,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move next comments to current field
|
||||
for (let i = 0; i < fields.length - 1; i++) {
|
||||
const currentField = fields[i];
|
||||
const nextField = fields[i + 1];
|
||||
const currentFieldEndLine = (currentField.loc as TextLocation).end.line;
|
||||
const nextFieldFirstComment = nextField.comments[0];
|
||||
|
||||
if (
|
||||
nextFieldFirstComment &&
|
||||
(nextFieldFirstComment.loc as TextLocation).end.line ===
|
||||
currentFieldEndLine
|
||||
) {
|
||||
const dislocatedComment = nextField.comments.shift() as Comment;
|
||||
currentField.comments.push(dislocatedComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reviseFuncComments(functions: FunctionDefinition[]): void {
|
||||
if (functions.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < functions.length - 1; i++) {
|
||||
const currentFunc = functions[i];
|
||||
const nextFunc = functions[i + 1];
|
||||
const currentFuncEndLine = (currentFunc.loc as TextLocation).end.line;
|
||||
const nextFuncFirstComment = nextFunc.comments[0];
|
||||
|
||||
if (
|
||||
nextFuncFirstComment &&
|
||||
(nextFuncFirstComment.loc as TextLocation).end.line === currentFuncEndLine
|
||||
) {
|
||||
const dislocatedComment = nextFunc.comments.shift() as Comment;
|
||||
currentFunc.comments.push(dislocatedComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getUnifyNamespace(
|
||||
looseAbsoluteFilePath: string,
|
||||
astNamespaces?: t.NamespaceDefinition[],
|
||||
): {
|
||||
namespace: string;
|
||||
unifyNamespace: string;
|
||||
} {
|
||||
let namespace = '';
|
||||
let unifyNamespace = '';
|
||||
if (astNamespaces && astNamespaces.length > 0) {
|
||||
const namespaceMap: Record<string, t.NamespaceDefinition> = {};
|
||||
for (const astNamespace of astNamespaces) {
|
||||
const scopeName = astNamespace.scope.value;
|
||||
namespaceMap[scopeName] = astNamespace;
|
||||
}
|
||||
|
||||
const astNamespaceCurrent =
|
||||
namespaceMap.js || namespaceMap.go || namespaceMap.py;
|
||||
if (astNamespaceCurrent) {
|
||||
namespace = astNamespaceCurrent.name.value;
|
||||
unifyNamespace = namespace;
|
||||
} else if (namespaceMap.java) {
|
||||
namespace = namespaceMap.java.name.value.split('.').pop() as string;
|
||||
unifyNamespace = namespace;
|
||||
} else {
|
||||
const message = 'a js namespace should be specifed';
|
||||
const fullFilePath = path.resolve(
|
||||
rootDir,
|
||||
`${looseAbsoluteFilePath}.thrift`,
|
||||
);
|
||||
const infoMessage = `${message} (${fullFilePath})`;
|
||||
logAndThrowError(infoMessage, message);
|
||||
}
|
||||
} else {
|
||||
namespace = '';
|
||||
unifyNamespace = namespaceDefault;
|
||||
}
|
||||
|
||||
unifyNamespace = unifyNamespace
|
||||
.replace(/\./g, '_')
|
||||
.replace(/[^a-zA-Z0-9_]/g, '');
|
||||
|
||||
return { namespace, unifyNamespace };
|
||||
}
|
||||
|
||||
function createAddNamespaceReferValue(
|
||||
filenameNamespaceMap: Record<string, string>,
|
||||
namespace: string,
|
||||
): (fieldType: FunctionType) => void {
|
||||
const regExpNamespaceMap = new Map<RegExp, string>();
|
||||
Object.keys(filenameNamespaceMap).forEach(filename => {
|
||||
const regExp = new RegExp(`^${filename}(\\.[^\\.]*)$`);
|
||||
regExpNamespaceMap.set(regExp, filenameNamespaceMap[filename]);
|
||||
});
|
||||
|
||||
function addNamespaceReferValue(fieldType: FunctionType): void {
|
||||
if ((fieldType as Identifier).type === SyntaxType.Identifier) {
|
||||
const identifierValue = (fieldType as Identifier).value;
|
||||
|
||||
if (!identifierValue.includes('.')) {
|
||||
(fieldType as Identifier).namespaceValue =
|
||||
`${namespace}.${identifierValue}`;
|
||||
} else {
|
||||
const parts = identifierValue.split('.');
|
||||
if (parts.length === 2 && enumNames.includes(parts[0])) {
|
||||
(fieldType as Identifier).namespaceValue =
|
||||
`${namespace}.${identifierValue}`;
|
||||
} else {
|
||||
for (const regExp of regExpNamespaceMap.keys()) {
|
||||
if (regExp.test(identifierValue)) {
|
||||
const namespaceName = regExpNamespaceMap.get(regExp);
|
||||
(fieldType as Identifier).namespaceValue =
|
||||
identifierValue.replace(regExp, `${namespaceName}$1`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ((fieldType as ContainerType).valueType) {
|
||||
addNamespaceReferValue(
|
||||
(fieldType as ContainerType).valueType as FunctionType,
|
||||
);
|
||||
|
||||
if ((fieldType as MapType).keyType) {
|
||||
addNamespaceReferValue((fieldType as MapType).keyType as FunctionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return addNamespaceReferValue;
|
||||
}
|
||||
|
||||
function getAddNamespaceReferValue(
|
||||
astIncludes: t.IncludeDefinition[],
|
||||
namespace: string,
|
||||
): ((fieldType: FunctionType) => void) | undefined {
|
||||
const filenameNamespaceMap: Record<string, string> = {};
|
||||
|
||||
for (const astInclude of astIncludes) {
|
||||
const idlFilePath = astInclude.path.value;
|
||||
const looseIdlFilePath = idlFilePath.replace(/\.thrift$/, '');
|
||||
const looseFilename = looseIdlFilePath.split('/').pop() as string;
|
||||
// try relative path
|
||||
let looseAbsoluteFilePath = getPosixPath(
|
||||
path.join(path.dirname(entryLooseAbsoluteFilePath), looseIdlFilePath),
|
||||
);
|
||||
|
||||
// try absulote path
|
||||
const alternativeLooseAbsoluteFilePath = looseIdlFilePath;
|
||||
|
||||
let document =
|
||||
fileDocumentMap[looseAbsoluteFilePath] ||
|
||||
fileDocumentMap[alternativeLooseAbsoluteFilePath];
|
||||
if (!document) {
|
||||
let content = '';
|
||||
if (absoluteFileContentMap) {
|
||||
content = absoluteFileContentMap[`${looseAbsoluteFilePath}.thrift`];
|
||||
if (typeof content === 'undefined') {
|
||||
content =
|
||||
absoluteFileContentMap[
|
||||
`${alternativeLooseAbsoluteFilePath}.thrift`
|
||||
];
|
||||
if (typeof content === 'undefined') {
|
||||
logAndThrowError(
|
||||
`file ${looseAbsoluteFilePath}.thrift does not exist in fileContentMap`,
|
||||
);
|
||||
}
|
||||
|
||||
looseAbsoluteFilePath = alternativeLooseAbsoluteFilePath;
|
||||
}
|
||||
} else {
|
||||
let fullFilePath = getPosixPath(
|
||||
path.resolve(rootDir, `${looseAbsoluteFilePath}.thrift`),
|
||||
);
|
||||
|
||||
// Search
|
||||
if (!fs.existsSync(fullFilePath)) {
|
||||
const filePaths = [rootDir, ...searchPaths].map(searchPath =>
|
||||
getPosixPath(
|
||||
path.resolve(
|
||||
rootDir,
|
||||
searchPath,
|
||||
`${alternativeLooseAbsoluteFilePath}.thrift`,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const existedFilePath = filePaths.find(filePath =>
|
||||
fs.existsSync(filePath),
|
||||
);
|
||||
if (typeof existedFilePath === 'undefined') {
|
||||
logAndThrowError(`file ${filePaths[0]} does not exist`);
|
||||
} else {
|
||||
fullFilePath = existedFilePath;
|
||||
looseAbsoluteFilePath = path
|
||||
.relative(rootDir, existedFilePath)
|
||||
.replace(/\.thrift$/, '');
|
||||
}
|
||||
}
|
||||
|
||||
content = fs.readFileSync(fullFilePath, 'utf8');
|
||||
}
|
||||
|
||||
document = parseContent(content, looseAbsoluteFilePath);
|
||||
} else if (!fileDocumentMap[looseAbsoluteFilePath]) {
|
||||
looseAbsoluteFilePath = alternativeLooseAbsoluteFilePath;
|
||||
}
|
||||
|
||||
const astNamespaces: t.NamespaceDefinition[] = [];
|
||||
for (const statement of document.body) {
|
||||
if (statement.type === t.SyntaxType.NamespaceDefinition) {
|
||||
astNamespaces.push(statement as t.NamespaceDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
const { unifyNamespace } = getUnifyNamespace(
|
||||
looseAbsoluteFilePath,
|
||||
astNamespaces,
|
||||
);
|
||||
filenameNamespaceMap[looseFilename] = unifyNamespace;
|
||||
}
|
||||
|
||||
return createAddNamespaceReferValue(filenameNamespaceMap, namespace);
|
||||
}
|
||||
|
||||
function convertTypedefDefinition(
|
||||
astTypedef: TypedefDefinition,
|
||||
): TypedefDefinition {
|
||||
const typedefDefinition: TypedefDefinition = { ...astTypedef };
|
||||
// const { name, definitionType} = typedefDefinition
|
||||
if (typeof addNamespaceValue === 'function') {
|
||||
addNamespaceValue(typedefDefinition.definitionType);
|
||||
addNamespaceValue(typedefDefinition.name);
|
||||
}
|
||||
|
||||
return typedefDefinition;
|
||||
}
|
||||
|
||||
function addNamespaceValueToConstValue(constValue: ConstValue) {
|
||||
if (typeof addNamespaceValue === 'function') {
|
||||
if (constValue.type === SyntaxType.Identifier) {
|
||||
addNamespaceValue(constValue);
|
||||
} else if (constValue.type === SyntaxType.ConstMap) {
|
||||
for (const property of constValue.properties) {
|
||||
addNamespaceValueToConstValue(property.name);
|
||||
addNamespaceValueToConstValue(property.initializer);
|
||||
}
|
||||
} else if (constValue.type === SyntaxType.ConstList) {
|
||||
for (const element of constValue.elements) {
|
||||
addNamespaceValueToConstValue(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertConstDefinition(astConst: ConstDefinition): ConstDefinition {
|
||||
const constDefinition: ConstDefinition = { ...astConst };
|
||||
if (typeof addNamespaceValue === 'function') {
|
||||
addNamespaceValue(constDefinition.name);
|
||||
addNamespaceValue(constDefinition.fieldType);
|
||||
addNamespaceValueToConstValue(constDefinition.initializer);
|
||||
}
|
||||
|
||||
return constDefinition;
|
||||
}
|
||||
|
||||
function convertStructDefinition(
|
||||
astStruct: StructDefinition,
|
||||
): StructDefinition {
|
||||
const structDefinition: StructDefinition = { ...astStruct };
|
||||
const { name, fields } = structDefinition;
|
||||
const newName = name;
|
||||
if (addNamespaceValue) {
|
||||
addNamespaceValue(newName);
|
||||
}
|
||||
|
||||
for (const field of fields) {
|
||||
const { fieldType, annotations } = field;
|
||||
const fieldName = field.name.value;
|
||||
if (addNamespaceValue) {
|
||||
addNamespaceValue(fieldType);
|
||||
}
|
||||
|
||||
field.extensionConfig = getFieldExtensionConfig(
|
||||
fieldName,
|
||||
fieldType,
|
||||
annotations,
|
||||
);
|
||||
|
||||
if ((field.extensionConfig.tag || '').includes('omitempty')) {
|
||||
field.requiredness = 'optional';
|
||||
}
|
||||
}
|
||||
|
||||
reviseFieldComments(fields);
|
||||
const newFields: FieldDefinition[] = [];
|
||||
for (const field of fields) {
|
||||
const tag = (field.extensionConfig && field.extensionConfig.tag) || '';
|
||||
if (tag.includes('int2str')) {
|
||||
field.fieldType = convertIntToString(field.fieldType as FieldType);
|
||||
}
|
||||
|
||||
if (ignoreGoTagDash || !tag.includes('ignore')) {
|
||||
newFields.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
structDefinition.fields = newFields;
|
||||
structDefinition.name = newName;
|
||||
return structDefinition;
|
||||
}
|
||||
|
||||
function convertEnumDefinition(astEnum: EnumDefinition): EnumDefinition {
|
||||
const enumDefinition: EnumDefinition = { ...astEnum };
|
||||
const { name, members } = enumDefinition;
|
||||
enumNames.push(name.value);
|
||||
reviseFieldComments(members);
|
||||
if (addNamespaceValue) {
|
||||
addNamespaceValue(name);
|
||||
}
|
||||
|
||||
return enumDefinition;
|
||||
}
|
||||
|
||||
function convertFunctionDefinition(
|
||||
astFunc: FunctionDefinition,
|
||||
): FunctionDefinition {
|
||||
const functionDefinition: FunctionDefinition = { ...astFunc };
|
||||
const { returnType, fields, annotations, name } = functionDefinition;
|
||||
|
||||
if (addNamespaceValue) {
|
||||
addNamespaceValue(name);
|
||||
addNamespaceValue(returnType);
|
||||
for (const field of fields) {
|
||||
addNamespaceValue(field.fieldType);
|
||||
}
|
||||
}
|
||||
|
||||
functionDefinition.extensionConfig = getFuncExtensionConfig(annotations);
|
||||
return functionDefinition;
|
||||
}
|
||||
|
||||
function convertServiceDefinition(
|
||||
astService: ServiceDefinition,
|
||||
): ServiceDefinition {
|
||||
const serviceDefinition: ServiceDefinition = { ...astService };
|
||||
const { annotations, name } = serviceDefinition;
|
||||
const functions: FunctionDefinition[] = [];
|
||||
for (const astFunc of astService.functions) {
|
||||
functions.push(convertFunctionDefinition(astFunc));
|
||||
}
|
||||
|
||||
if (addNamespaceValue) {
|
||||
addNamespaceValue(name);
|
||||
}
|
||||
|
||||
reviseFuncComments(functions);
|
||||
serviceDefinition.functions = functions;
|
||||
serviceDefinition.extensionConfig = getServiceExtensionConfig(annotations);
|
||||
return serviceDefinition;
|
||||
}
|
||||
|
||||
function parseContent(
|
||||
content: string,
|
||||
looseAbsoluteFilePath: string,
|
||||
): t.ThriftDocument {
|
||||
if (fileDocumentMap[looseAbsoluteFilePath]) {
|
||||
return fileDocumentMap[looseAbsoluteFilePath];
|
||||
}
|
||||
|
||||
const document: t.ThriftDocument | t.ThriftErrors = t.parse(content);
|
||||
if ((document as t.ThriftErrors).type === t.SyntaxType.ThriftErrors) {
|
||||
const error = (document as t.ThriftErrors).errors[0];
|
||||
const { start } = error.loc;
|
||||
const fullFilePath = path.resolve(
|
||||
rootDir,
|
||||
`${looseAbsoluteFilePath}.thrift`,
|
||||
);
|
||||
const message = `${error.message}(${fullFilePath}:${start.line}:${start.column})`;
|
||||
logAndThrowError(message, error.message);
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
fileDocumentMap[looseAbsoluteFilePath] = document as t.ThriftDocument;
|
||||
}
|
||||
|
||||
return document as t.ThriftDocument;
|
||||
}
|
||||
|
||||
export function parseThriftContent(
|
||||
content: string,
|
||||
option: {
|
||||
loosePath: string;
|
||||
rootDir: string;
|
||||
cache: boolean;
|
||||
searchPaths: string[];
|
||||
namespaceRefer: boolean;
|
||||
ignoreGoTag: boolean;
|
||||
ignoreGoTagDash: boolean;
|
||||
},
|
||||
fileContentMap?: Record<string, string>,
|
||||
): UnifyDocument {
|
||||
rootDir = option.rootDir;
|
||||
entryLooseAbsoluteFilePath = option.loosePath;
|
||||
cache = option.cache;
|
||||
ignoreGoTag = option.ignoreGoTag;
|
||||
ignoreGoTagDash = option.ignoreGoTagDash;
|
||||
searchPaths = option.searchPaths;
|
||||
absoluteFileContentMap = fileContentMap;
|
||||
addNamespaceValue = undefined;
|
||||
enumNames = [];
|
||||
|
||||
// parse file content
|
||||
const document = parseContent(content, entryLooseAbsoluteFilePath);
|
||||
const statementGroup: Record<string, t.ThriftStatement[]> = {};
|
||||
for (const statement of (document as t.ThriftDocument).body) {
|
||||
let { type } = statement;
|
||||
|
||||
// NOTE: in the latest version of Thrift, union is similar to struct except that all fields are converted to 'optional'.
|
||||
// the idl parse shields the difference, so we can dispose them together.
|
||||
if (type === t.SyntaxType.UnionDefinition) {
|
||||
type = t.SyntaxType.StructDefinition;
|
||||
statement.type = t.SyntaxType.StructDefinition;
|
||||
}
|
||||
|
||||
if (!statementGroup[type]) {
|
||||
statementGroup[type] = [statement];
|
||||
} else {
|
||||
statementGroup[type].push(statement);
|
||||
}
|
||||
}
|
||||
|
||||
const { unifyNamespace, namespace } = getUnifyNamespace(
|
||||
entryLooseAbsoluteFilePath,
|
||||
statementGroup[t.SyntaxType.NamespaceDefinition] as t.NamespaceDefinition[],
|
||||
);
|
||||
if (option.namespaceRefer) {
|
||||
addNamespaceValue = getAddNamespaceReferValue(
|
||||
(statementGroup[t.SyntaxType.IncludeDefinition] ||
|
||||
[]) as t.IncludeDefinition[],
|
||||
unifyNamespace,
|
||||
);
|
||||
}
|
||||
|
||||
const statements: UnifyStatement[] = [];
|
||||
if (statementGroup[t.SyntaxType.TypedefDefinition]) {
|
||||
for (const astTypedef of statementGroup[t.SyntaxType.TypedefDefinition]) {
|
||||
statements.push(convertTypedefDefinition(astTypedef as any));
|
||||
}
|
||||
}
|
||||
|
||||
if (statementGroup[SyntaxType.EnumDefinition]) {
|
||||
for (const astEnum of statementGroup[t.SyntaxType.EnumDefinition]) {
|
||||
statements.push(convertEnumDefinition(astEnum as any));
|
||||
}
|
||||
}
|
||||
|
||||
if (statementGroup[t.SyntaxType.ConstDefinition]) {
|
||||
for (const astConst of statementGroup[t.SyntaxType.ConstDefinition]) {
|
||||
statements.push(convertConstDefinition(astConst as any));
|
||||
}
|
||||
}
|
||||
|
||||
if (statementGroup[SyntaxType.StructDefinition]) {
|
||||
for (const astStruct of statementGroup[t.SyntaxType.StructDefinition]) {
|
||||
statements.push(convertStructDefinition(astStruct as any));
|
||||
}
|
||||
}
|
||||
|
||||
if (statementGroup[SyntaxType.ServiceDefinition]) {
|
||||
for (const astService of statementGroup[t.SyntaxType.ServiceDefinition]) {
|
||||
statements.push(convertServiceDefinition(astService as any));
|
||||
}
|
||||
}
|
||||
|
||||
const includes: string[] = [];
|
||||
if (statementGroup[t.SyntaxType.IncludeDefinition]) {
|
||||
for (const astInclude of statementGroup[t.SyntaxType.IncludeDefinition]) {
|
||||
includes.push((astInclude as t.IncludeDefinition).path.value);
|
||||
}
|
||||
}
|
||||
|
||||
const unifyDocument: UnifyDocument = {
|
||||
type: SyntaxType.UnifyDocument,
|
||||
namespace,
|
||||
unifyNamespace,
|
||||
includes,
|
||||
statements,
|
||||
includeRefer: {},
|
||||
};
|
||||
|
||||
return unifyDocument;
|
||||
}
|
||||
407
frontend/infra/idl/idl-parser/src/unify/type.ts
Normal file
407
frontend/infra/idl/idl-parser/src/unify/type.ts
Normal file
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
type FieldExtensionConfig,
|
||||
type ServiceExtensionConfig,
|
||||
type FunctionExtensionConfig,
|
||||
} from '../common/extension_type';
|
||||
|
||||
export * from '../common/extension_type';
|
||||
|
||||
export interface Node {
|
||||
type: SyntaxType;
|
||||
}
|
||||
|
||||
export interface SyntaxNode extends Node {
|
||||
loc?: TextLocation;
|
||||
}
|
||||
|
||||
export interface StructLike {
|
||||
name: Identifier;
|
||||
fields: Array<FieldDefinition>;
|
||||
annotations?: Annotations;
|
||||
comments: Array<Comment>;
|
||||
loc: TextLocation;
|
||||
}
|
||||
|
||||
export interface TextLocation {
|
||||
start: TextPosition;
|
||||
end: TextPosition;
|
||||
}
|
||||
|
||||
export interface TextPosition {
|
||||
line: number;
|
||||
column: number;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface Token extends SyntaxNode {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface UnifyDocument extends Node {
|
||||
type: SyntaxType.UnifyDocument;
|
||||
statements: UnifyStatement[];
|
||||
namespace: string;
|
||||
unifyNamespace: string;
|
||||
includes: string[];
|
||||
includeRefer: Record<string, string>;
|
||||
}
|
||||
|
||||
export type UnifyStatement =
|
||||
| EnumDefinition
|
||||
| StructDefinition
|
||||
| TypedefDefinition
|
||||
| ConstDefinition
|
||||
| ServiceDefinition;
|
||||
|
||||
export type CommentType = SyntaxType.CommentLine | SyntaxType.CommentBlock;
|
||||
|
||||
export type Comment = CommentLine | CommentBlock;
|
||||
|
||||
export interface CommentLine extends SyntaxNode {
|
||||
type: SyntaxType.CommentLine;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface CommentBlock extends SyntaxNode {
|
||||
type: SyntaxType.CommentBlock;
|
||||
value: Array<string>;
|
||||
}
|
||||
|
||||
export interface Annotations extends SyntaxNode {
|
||||
annotations: Array<Annotation>;
|
||||
}
|
||||
|
||||
export interface Annotation extends SyntaxNode {
|
||||
name: Identifier;
|
||||
value?: StringLiteral;
|
||||
}
|
||||
|
||||
export interface PrimarySyntax extends SyntaxNode {
|
||||
comments: Array<Comment>;
|
||||
}
|
||||
|
||||
export type FieldType = BaseType | ContainerType | Identifier;
|
||||
|
||||
export type FunctionType = FieldType | VoidType;
|
||||
|
||||
export type KeywordType =
|
||||
| SyntaxType.StringKeyword
|
||||
| SyntaxType.DoubleKeyword
|
||||
| SyntaxType.BoolKeyword
|
||||
| SyntaxType.I8Keyword
|
||||
| SyntaxType.I16Keyword
|
||||
| SyntaxType.I32Keyword
|
||||
| SyntaxType.I64Keyword
|
||||
| SyntaxType.BinaryKeyword
|
||||
| SyntaxType.ByteKeyword;
|
||||
|
||||
export interface VoidType extends SyntaxNode {
|
||||
type: SyntaxType.VoidKeyword;
|
||||
}
|
||||
|
||||
export type ContainerType = SetType | MapType | ListType;
|
||||
|
||||
export interface BaseType extends SyntaxNode {
|
||||
type: KeywordType;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface SetType extends SyntaxNode {
|
||||
type: SyntaxType.SetType;
|
||||
valueType: FieldType;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface ListType extends SyntaxNode {
|
||||
type: SyntaxType.ListType;
|
||||
valueType: FieldType;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface MapType extends SyntaxNode {
|
||||
type: SyntaxType.MapType;
|
||||
keyType: FieldType;
|
||||
valueType: FieldType;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export type ConstValue =
|
||||
| StringLiteral
|
||||
| IntConstant
|
||||
| DoubleConstant
|
||||
| BooleanLiteral
|
||||
| ConstMap
|
||||
| ConstList
|
||||
| Identifier;
|
||||
|
||||
export interface ConstDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.ConstDefinition;
|
||||
name: Identifier;
|
||||
fieldType: FieldType;
|
||||
initializer: ConstValue;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export type FieldRequired = 'required' | 'optional';
|
||||
|
||||
export interface InterfaceWithFields extends PrimarySyntax {
|
||||
name: Identifier;
|
||||
fields: Array<FieldDefinition>;
|
||||
annotations?: Annotations;
|
||||
options?: Record<string, string>;
|
||||
nested?: Record<string, EnumDefinition | StructDefinition>;
|
||||
}
|
||||
|
||||
export interface StructDefinition extends InterfaceWithFields {
|
||||
type: SyntaxType.StructDefinition;
|
||||
}
|
||||
|
||||
export interface FieldDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.FieldDefinition;
|
||||
name: Identifier;
|
||||
fieldID: FieldID | null;
|
||||
fieldType: FunctionType;
|
||||
requiredness?: FieldRequired | null;
|
||||
defaultValue?: ConstValue | null;
|
||||
annotations?: Annotations;
|
||||
options?: Record<string, string>;
|
||||
extensionConfig?: FieldExtensionConfig;
|
||||
}
|
||||
|
||||
export interface FieldID extends SyntaxNode {
|
||||
type: SyntaxType.FieldID;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface EnumDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.EnumDefinition;
|
||||
name: Identifier;
|
||||
members: Array<EnumMember>;
|
||||
annotations?: Annotations;
|
||||
options?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface EnumMember extends PrimarySyntax {
|
||||
type: SyntaxType.EnumMember;
|
||||
name: Identifier;
|
||||
initializer?: IntConstant | null;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface TypedefDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.TypedefDefinition;
|
||||
name: Identifier;
|
||||
definitionType: FieldType;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export interface ServiceDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.ServiceDefinition;
|
||||
name: Identifier;
|
||||
extends?: Identifier | null;
|
||||
functions: Array<FunctionDefinition>;
|
||||
annotations?: Annotations;
|
||||
options?: Record<string, string>;
|
||||
extensionConfig?: ServiceExtensionConfig;
|
||||
}
|
||||
|
||||
export interface FunctionDefinition extends PrimarySyntax {
|
||||
type: SyntaxType.FunctionDefinition;
|
||||
name: Identifier;
|
||||
oneway: boolean;
|
||||
returnType: FunctionType;
|
||||
fields: Array<FieldDefinition>;
|
||||
throws: Array<FieldDefinition>;
|
||||
modifiers: Array<Token>;
|
||||
annotations?: Annotations;
|
||||
options?: Record<string, string>;
|
||||
extensionConfig?: FunctionExtensionConfig;
|
||||
}
|
||||
|
||||
export interface ParametersDefinition extends SyntaxNode {
|
||||
type: SyntaxType.ParametersDefinition;
|
||||
fields: Array<FieldDefinition>;
|
||||
}
|
||||
|
||||
export interface StringLiteral extends SyntaxNode {
|
||||
type: SyntaxType.StringLiteral;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface BooleanLiteral extends SyntaxNode {
|
||||
type: SyntaxType.BooleanLiteral;
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
export interface IntegerLiteral extends SyntaxNode {
|
||||
type: SyntaxType.IntegerLiteral;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface HexLiteral extends SyntaxNode {
|
||||
type: SyntaxType.HexLiteral;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface FloatLiteral extends SyntaxNode {
|
||||
type: SyntaxType.FloatLiteral;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ExponentialLiteral extends SyntaxNode {
|
||||
type: SyntaxType.ExponentialLiteral;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IntConstant extends SyntaxNode {
|
||||
type: SyntaxType.IntConstant;
|
||||
value: IntegerLiteral | HexLiteral;
|
||||
}
|
||||
|
||||
export interface DoubleConstant extends SyntaxNode {
|
||||
type: SyntaxType.DoubleConstant;
|
||||
value: FloatLiteral | ExponentialLiteral;
|
||||
}
|
||||
|
||||
export interface ConstMap extends SyntaxNode {
|
||||
type: SyntaxType.ConstMap;
|
||||
properties: Array<PropertyAssignment>;
|
||||
}
|
||||
|
||||
export interface ConstList extends SyntaxNode {
|
||||
type: SyntaxType.ConstList;
|
||||
elements: Array<ConstValue>;
|
||||
}
|
||||
|
||||
export interface PropertyAssignment extends SyntaxNode {
|
||||
type: SyntaxType.PropertyAssignment;
|
||||
name: ConstValue;
|
||||
initializer: ConstValue;
|
||||
}
|
||||
|
||||
export interface Identifier extends SyntaxNode {
|
||||
type: SyntaxType.Identifier;
|
||||
value: string;
|
||||
|
||||
// value with one level namespace
|
||||
namespaceValue?: string;
|
||||
annotations?: Annotations;
|
||||
}
|
||||
|
||||
export enum SyntaxType {
|
||||
UnifyDocument = 'UnifyDocument',
|
||||
|
||||
Identifier = 'Identifier',
|
||||
FieldID = 'FieldID',
|
||||
|
||||
// Statements
|
||||
ConstDefinition = 'ConstDefinition',
|
||||
StructDefinition = 'StructDefinition',
|
||||
EnumDefinition = 'EnumDefinition',
|
||||
ServiceDefinition = 'ServiceDefinition',
|
||||
TypedefDefinition = 'TypedefDefinition',
|
||||
|
||||
// Fields
|
||||
FieldDefinition = 'FieldDefinition',
|
||||
FunctionDefinition = 'FunctionDefinition',
|
||||
ParametersDefinition = 'ParametersDefinition',
|
||||
|
||||
// Type Annotations
|
||||
FieldType = 'FieldType',
|
||||
BaseType = 'BaseType',
|
||||
SetType = 'SetType',
|
||||
MapType = 'MapType',
|
||||
ListType = 'ListType',
|
||||
|
||||
// Values
|
||||
ConstValue = 'ConstValue',
|
||||
IntConstant = 'IntConstant',
|
||||
DoubleConstant = 'DoubleConstant',
|
||||
|
||||
ConstList = 'ConstList',
|
||||
ConstMap = 'ConstMap',
|
||||
EnumMember = 'EnumMember',
|
||||
|
||||
// Literals
|
||||
CommentLine = 'CommentLine',
|
||||
CommentBlock = 'CommentBlock',
|
||||
StringLiteral = 'StringLiteral',
|
||||
IntegerLiteral = 'IntegerLiteral',
|
||||
FloatLiteral = 'FloatLiteral',
|
||||
HexLiteral = 'HexLiteral',
|
||||
ExponentialLiteral = 'ExponentialLiteral',
|
||||
BooleanLiteral = 'BooleanLiteral',
|
||||
PropertyAssignment = 'PropertyAssignment',
|
||||
|
||||
// Tokens
|
||||
LeftParenToken = 'LeftParenToken',
|
||||
RightParenToken = 'RightParenToken',
|
||||
LeftBraceToken = 'LeftBraceToken',
|
||||
RightBraceToken = 'RightBraceToken',
|
||||
LeftBracketToken = 'LeftBracketToken',
|
||||
RightBracketToken = 'RightBracketToken',
|
||||
CommaToken = 'CommaToken',
|
||||
DotToken = 'DotToken',
|
||||
MinusToken = 'MinusToken',
|
||||
SemicolonToken = 'SemicolonToken',
|
||||
ColonToken = 'ColonToken',
|
||||
StarToken = 'StarToken',
|
||||
EqualToken = 'EqualToken',
|
||||
LessThanToken = 'LessThanToken',
|
||||
GreaterThanToken = 'GreaterThanToken',
|
||||
|
||||
// Keywords
|
||||
NamespaceKeyword = 'NamespaceKeyword',
|
||||
IncludeKeyword = 'IncludeKeyword',
|
||||
CppIncludeKeyword = 'CppIncludeKeyword',
|
||||
ExceptionKeyword = 'ExceptionKeyword',
|
||||
ServiceKeyword = 'ServiceKeyword',
|
||||
ExtendsKeyword = 'ExtendsKeyword',
|
||||
RequiredKeyword = 'RequiredKeyword',
|
||||
OptionalKeyword = 'OptionalKeyword',
|
||||
FalseKeyword = 'FalseKeyword',
|
||||
TrueKeyword = 'TrueKeyword',
|
||||
ConstKeyword = 'ConstKeyword',
|
||||
DoubleKeyword = 'DoubleKeyword',
|
||||
StructKeyword = 'StructKeyword',
|
||||
TypedefKeyword = 'TypedefKeyword',
|
||||
UnionKeyword = 'UnionKeyword',
|
||||
StringKeyword = 'StringKeyword',
|
||||
BinaryKeyword = 'BinaryKeyword',
|
||||
BoolKeyword = 'BoolKeyword',
|
||||
ByteKeyword = 'ByteKeyword',
|
||||
EnumKeyword = 'EnumKeyword',
|
||||
SenumKeyword = 'SenumKeyword',
|
||||
ListKeyword = 'ListKeyword',
|
||||
SetKeyword = 'SetKeyword',
|
||||
MapKeyword = 'MapKeyword',
|
||||
I8Keyword = 'I8Keyword',
|
||||
I16Keyword = 'I16Keyword',
|
||||
I32Keyword = 'I32Keyword',
|
||||
I64Keyword = 'I64Keyword',
|
||||
ThrowsKeyword = 'ThrowsKeyword',
|
||||
VoidKeyword = 'VoidKeyword',
|
||||
OnewayKeyword = 'OnewayKeyword',
|
||||
|
||||
// Other
|
||||
Annotation = 'Annotation',
|
||||
Annotations = 'Annotations',
|
||||
|
||||
EOF = 'EOF',
|
||||
}
|
||||
41
frontend/infra/idl/idl-parser/src/unify/util.ts
Normal file
41
frontend/infra/idl/idl-parser/src/unify/util.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SyntaxType, type ContainerType, type MapType, type FieldType } from './type';
|
||||
|
||||
export function convertIntToString(fType: FieldType): FieldType {
|
||||
const fieldType = { ...fType };
|
||||
const intTypes = [
|
||||
SyntaxType.I8Keyword,
|
||||
SyntaxType.I16Keyword,
|
||||
SyntaxType.I32Keyword,
|
||||
SyntaxType.I64Keyword,
|
||||
];
|
||||
if (intTypes.includes(fieldType.type)) {
|
||||
fieldType.type = SyntaxType.StringKeyword;
|
||||
} else if ((fieldType as ContainerType).valueType) {
|
||||
(fieldType as ContainerType).valueType = convertIntToString(
|
||||
(fieldType as ContainerType).valueType,
|
||||
);
|
||||
if ((fieldType as MapType).keyType) {
|
||||
(fieldType as MapType).keyType = convertIntToString(
|
||||
(fieldType as MapType).keyType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return fieldType;
|
||||
}
|
||||
45
frontend/infra/idl/idl-parser/src/utils/index.ts
Normal file
45
frontend/infra/idl/idl-parser/src/utils/index.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { logger } from '@coze-arch/rush-logger';
|
||||
|
||||
export const logAndThrowError = (logMessage: string, errorMessage?: string) => {
|
||||
logger.error(logMessage);
|
||||
const message = errorMessage || logMessage;
|
||||
throw new Error(message);
|
||||
};
|
||||
|
||||
export function mergeObject(
|
||||
target: { [key: string]: any },
|
||||
...sources: { [key: string]: any }[]
|
||||
): { [key: string]: any } {
|
||||
const newObj = { ...target };
|
||||
if (!sources) {return newObj;}
|
||||
|
||||
for (const source of sources) {
|
||||
for (const key of Object.keys(source)) {
|
||||
if (typeof source[key] !== 'undefined') {
|
||||
newObj[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newObj;
|
||||
}
|
||||
|
||||
export function getPosixPath(filePath: string) {
|
||||
return filePath.replace(/\\/g, '/');
|
||||
}
|
||||
Reference in New Issue
Block a user