feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
750
frontend/infra/idl/idl-parser/__tests__/unify.index.test.ts
Normal file
750
frontend/infra/idl/idl-parser/__tests__/unify.index.test.ts
Normal file
@@ -0,0 +1,750 @@
|
||||
/*
|
||||
* 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 t from '../src/unify';
|
||||
|
||||
describe('unify-parser', () => {
|
||||
describe('thrift index', () => {
|
||||
it('should parse a simple file', () => {
|
||||
const idl = path.resolve(__dirname, 'idl/index.thrift');
|
||||
const expected = { uri_prefix: 'https://example.com' };
|
||||
|
||||
const document = t.parse(idl, { cache: false });
|
||||
const { extensionConfig } = document.statements[0] as t.ServiceDefinition;
|
||||
return expect(extensionConfig).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should parse a complicate file', () => {
|
||||
const expected = {
|
||||
namespace: 'unify_idx',
|
||||
unifyNamespace: 'unify_idx',
|
||||
include: ['./unify_dependent1.thrift', 'unify_dependent2.thrift'],
|
||||
};
|
||||
|
||||
const document = t.parse('unify_index.thrift', {
|
||||
root: path.resolve(__dirname, './idl'),
|
||||
namespaceRefer: false,
|
||||
cache: false,
|
||||
});
|
||||
const target = {
|
||||
namespace: document.namespace,
|
||||
unifyNamespace: document.unifyNamespace,
|
||||
include: document.includes,
|
||||
};
|
||||
|
||||
return expect(target).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should parse a complicate file with relative path', () => {
|
||||
const expected = {
|
||||
include: ['base.thrift', 'basee.thrift'],
|
||||
};
|
||||
|
||||
const document = t.parse('dep/common.thrift', {
|
||||
root: path.resolve(__dirname, './idl'),
|
||||
namespaceRefer: false,
|
||||
cache: false,
|
||||
});
|
||||
const target = {
|
||||
include: document.includes,
|
||||
};
|
||||
|
||||
return expect(target).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should parse files from fileContentMap', () => {
|
||||
const indexContent = `
|
||||
include './unify_dependent.thrift'
|
||||
|
||||
typedef unify_dependent.Foo TFoo
|
||||
|
||||
union FuncRequest {
|
||||
1: unify_dependent.Foo r_key1
|
||||
2: TFoo r_key2
|
||||
}
|
||||
|
||||
service Example {
|
||||
unify_dependent.FuncResponse Func(1: FuncRequest req)
|
||||
} (
|
||||
)
|
||||
`;
|
||||
const dependentContent = `
|
||||
typedef Foo Foo1
|
||||
|
||||
struct Foo {
|
||||
1: string f_key1
|
||||
}
|
||||
|
||||
struct FuncResponse {
|
||||
}
|
||||
`;
|
||||
const fileContentMap = {
|
||||
'unify_index.thrift': indexContent,
|
||||
'unify_dependent.thrift': dependentContent,
|
||||
};
|
||||
|
||||
const expected = {
|
||||
namespace: '',
|
||||
unifyNamespace: 'root',
|
||||
include: ['./unify_dependent.thrift'],
|
||||
};
|
||||
|
||||
const document = t.parse(
|
||||
'unify_index.thrift',
|
||||
{ cache: false },
|
||||
fileContentMap,
|
||||
);
|
||||
const target = {
|
||||
namespace: document.namespace,
|
||||
unifyNamespace: document.unifyNamespace,
|
||||
include: document.includes,
|
||||
};
|
||||
|
||||
return expect(target).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should parse files from fileContentMap with relative path', () => {
|
||||
const indexContent = `
|
||||
include 'unify_dependent.thrift'
|
||||
|
||||
typedef unify_dependent.Foo TFoo
|
||||
|
||||
union FuncRequest {
|
||||
1: unify_dependent.Foo r_key1
|
||||
2: TFoo r_key2
|
||||
}
|
||||
|
||||
service Example {
|
||||
unify_dependent.FuncResponse Func(1: FuncRequest req)
|
||||
} (
|
||||
)
|
||||
`;
|
||||
const dependentContent = `
|
||||
typedef Foo Foo1
|
||||
|
||||
struct Foo {
|
||||
1: string f_key1
|
||||
}
|
||||
|
||||
struct FuncResponse {
|
||||
}
|
||||
`;
|
||||
const fileContentMap = {
|
||||
'relative/unify_index.thrift': indexContent,
|
||||
'relative/unify_dependent.thrift': dependentContent,
|
||||
};
|
||||
|
||||
const expected = {
|
||||
namespace: '',
|
||||
unifyNamespace: 'root',
|
||||
include: ['unify_dependent.thrift'],
|
||||
};
|
||||
|
||||
const document = t.parse(
|
||||
'relative/unify_index.thrift',
|
||||
{ cache: false },
|
||||
fileContentMap,
|
||||
);
|
||||
const target = {
|
||||
namespace: document.namespace,
|
||||
unifyNamespace: document.unifyNamespace,
|
||||
include: document.includes,
|
||||
};
|
||||
|
||||
return expect(target).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should parse files from fileContentMap and Java namespace', () => {
|
||||
const indexContent = `
|
||||
namespace java com.ferry.index
|
||||
|
||||
union FuncRequest {
|
||||
}
|
||||
|
||||
struct FuncResponse {}
|
||||
|
||||
service Example {
|
||||
FuncResponse Func(1: FuncRequest req)
|
||||
} (
|
||||
)
|
||||
`;
|
||||
|
||||
const fileContentMap = {
|
||||
'unify_index.thrift': indexContent,
|
||||
};
|
||||
|
||||
const expected = {
|
||||
namespace: 'index',
|
||||
unifyNamespace: 'index',
|
||||
include: [],
|
||||
};
|
||||
|
||||
const document = t.parse(
|
||||
'unify_index.thrift',
|
||||
{ cache: false },
|
||||
fileContentMap,
|
||||
);
|
||||
const target = {
|
||||
namespace: document.namespace,
|
||||
unifyNamespace: document.unifyNamespace,
|
||||
include: document.includes,
|
||||
};
|
||||
|
||||
return expect(target).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should parse files with cache', () => {
|
||||
const indexContent = `
|
||||
struct Foo {}
|
||||
`;
|
||||
const indexxContent = `
|
||||
`;
|
||||
|
||||
const rootContent = `
|
||||
include "unify_index_cache.thrift"
|
||||
`;
|
||||
|
||||
t.parse(
|
||||
'unify_index_cache.thrift',
|
||||
{
|
||||
cache: true,
|
||||
},
|
||||
{
|
||||
'unify_index_cache.thrift': indexContent,
|
||||
},
|
||||
);
|
||||
|
||||
t.parse(
|
||||
'unify_root.thrift',
|
||||
{ cache: false },
|
||||
{
|
||||
'unify_root.thrift': rootContent,
|
||||
},
|
||||
);
|
||||
|
||||
const document = t.parse(
|
||||
'unify_index_cache.thrift',
|
||||
{ cache: true },
|
||||
{
|
||||
'unify_index_cache.thrift': indexxContent,
|
||||
},
|
||||
);
|
||||
|
||||
return expect(document.statements[0].name.value).to.eql('Foo');
|
||||
});
|
||||
|
||||
it('should parse files with ignoreTag', () => {
|
||||
const indexContent = `
|
||||
struct Foo {
|
||||
1: string k1 (go.tag = "json:\\"key1\\"")
|
||||
}
|
||||
`;
|
||||
|
||||
const document = t.parse(
|
||||
'unify_index.thrift',
|
||||
{
|
||||
ignoreGoTag: true,
|
||||
},
|
||||
{
|
||||
'unify_index.thrift': indexContent,
|
||||
},
|
||||
);
|
||||
const { fields } = document.statements[0] as t.InterfaceWithFields;
|
||||
|
||||
return expect(fields[0].extensionConfig).to.eql({});
|
||||
});
|
||||
|
||||
it('should parse files with goTagDash', () => {
|
||||
const indexContent = `
|
||||
struct Foo {
|
||||
1: string k1 (go.tag = "json:\\"-\\"")
|
||||
}
|
||||
`;
|
||||
|
||||
const document = t.parse(
|
||||
'unify_index.thrift',
|
||||
{},
|
||||
{
|
||||
'unify_index.thrift': indexContent,
|
||||
},
|
||||
);
|
||||
const { fields } = document.statements[0] as t.InterfaceWithFields;
|
||||
|
||||
return expect(fields.length).to.eql(0);
|
||||
});
|
||||
|
||||
it('should parse files with ignoreTagDash', () => {
|
||||
const indexContent = `
|
||||
struct Foo {
|
||||
1: string k1 (go.tag = "json:\\"-\\"")
|
||||
}
|
||||
`;
|
||||
|
||||
const document = t.parse(
|
||||
'unify_index.thrift',
|
||||
{
|
||||
ignoreGoTagDash: true,
|
||||
},
|
||||
{
|
||||
'unify_index.thrift': indexContent,
|
||||
},
|
||||
);
|
||||
const { fields } = document.statements[0] as t.InterfaceWithFields;
|
||||
|
||||
return expect(fields.length).to.eql(1);
|
||||
});
|
||||
|
||||
it('should search files from searchPaths', () => {
|
||||
const idl = path.resolve(__dirname, 'idl/unify_search.thrift');
|
||||
const depDir = path.resolve(__dirname, 'idl/dep');
|
||||
|
||||
const document = t.parse(idl, {
|
||||
cache: false,
|
||||
searchPaths: [depDir],
|
||||
});
|
||||
|
||||
const struct = document.statements[0] as t.InterfaceWithFields;
|
||||
return expect(struct.type).to.eql(t.SyntaxType.StructDefinition);
|
||||
});
|
||||
|
||||
it('should search files from relative searchPaths', () => {
|
||||
const document = t.parse('unify_search.thrift', {
|
||||
root: path.resolve(__dirname, 'idl'),
|
||||
cache: false,
|
||||
searchPaths: ['./dep'],
|
||||
});
|
||||
|
||||
const struct = document.statements[0] as t.InterfaceWithFields;
|
||||
return expect(struct.type).to.eql(t.SyntaxType.StructDefinition);
|
||||
});
|
||||
|
||||
it('should parse files with a syntax error', () => {
|
||||
const indexContent = `
|
||||
struct
|
||||
`;
|
||||
|
||||
try {
|
||||
t.parse(
|
||||
'error.thrift',
|
||||
{ cache: false },
|
||||
{
|
||||
'error.thrift': indexContent,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
return expect(err.message).to.eql(
|
||||
'Struct-like must have an identifier',
|
||||
);
|
||||
}
|
||||
|
||||
return expect(0).to.eql(1);
|
||||
});
|
||||
|
||||
it('should parse files with a dependent file error within fileContentMap', () => {
|
||||
const indexContent = `
|
||||
include "./dependent.thrift"
|
||||
`;
|
||||
|
||||
try {
|
||||
t.parse(
|
||||
'error.thrift',
|
||||
{ cache: false },
|
||||
{
|
||||
'error.thrift': indexContent,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
return expect(err.message).to.eql(
|
||||
'file dependent.thrift does not exist in fileContentMap',
|
||||
);
|
||||
}
|
||||
|
||||
return expect(0).to.eql(1);
|
||||
});
|
||||
|
||||
it('should parse files with a entry file error within fileContentMap', () => {
|
||||
try {
|
||||
t.parse('specify_error.thrift', { cache: false }, {});
|
||||
} catch (err) {
|
||||
return expect(err.message).to.equal(
|
||||
'file "specify_error.thrift" does not exist in fileContentMap',
|
||||
);
|
||||
}
|
||||
|
||||
return expect(0).to.eql(1);
|
||||
});
|
||||
|
||||
it('should parse files with a entry file error', () => {
|
||||
const idl = path.resolve(__dirname, 'idl/special_error.thrift');
|
||||
|
||||
try {
|
||||
t.parse(idl, { cache: false });
|
||||
} catch (err) {
|
||||
return expect(err.message).to.contain('no such file:');
|
||||
}
|
||||
|
||||
return expect(0).to.eql(1);
|
||||
});
|
||||
|
||||
it('should parse files with a dependent file error', () => {
|
||||
const idl = path.resolve(__dirname, 'idl/unify_error.thrift');
|
||||
|
||||
try {
|
||||
t.parse(idl, { cache: false });
|
||||
} catch (err) {
|
||||
return expect(err.message).to.contain('does not exist');
|
||||
}
|
||||
|
||||
return expect(0).to.eql(1);
|
||||
});
|
||||
|
||||
it('should parse files with a namespace error', () => {
|
||||
const indexContent = `
|
||||
namespace java1 com.index
|
||||
`;
|
||||
|
||||
try {
|
||||
t.parse(
|
||||
'error.thrift',
|
||||
{ cache: false },
|
||||
{
|
||||
'error.thrift': indexContent,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
return expect(err.message).to.eql('a js namespace should be specifed');
|
||||
}
|
||||
|
||||
return expect(0).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('proto index', () => {
|
||||
it('should a simple file', () => {
|
||||
const idl = path.resolve(__dirname, 'idl/index.proto');
|
||||
const expected = { uri_prefix: '//example.com' };
|
||||
const document = t.parse(idl, { cache: false });
|
||||
const { extensionConfig } = document.statements[0] as t.ServiceDefinition;
|
||||
return expect(extensionConfig).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should parse a complicate file', () => {
|
||||
const expected = {
|
||||
namespace: 'unify_idx',
|
||||
unifyNamespace: 'unify_idx',
|
||||
include: ['./unify_dependent1.proto', 'unify_dependent2.proto'],
|
||||
};
|
||||
|
||||
const document = t.parse('unify_index.proto', {
|
||||
root: path.resolve(__dirname, './idl'),
|
||||
namespaceRefer: false,
|
||||
cache: false,
|
||||
});
|
||||
const target = {
|
||||
namespace: document.namespace,
|
||||
unifyNamespace: document.unifyNamespace,
|
||||
include: document.includes,
|
||||
};
|
||||
|
||||
return expect(target).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should parse a complicate file with relative path', () => {
|
||||
const expected = {
|
||||
include: ['base.proto', 'basee.proto'],
|
||||
};
|
||||
|
||||
const document = t.parse('dep/common.proto', {
|
||||
root: path.resolve(__dirname, './idl'),
|
||||
namespaceRefer: false,
|
||||
cache: false,
|
||||
});
|
||||
const target = {
|
||||
include: document.includes,
|
||||
};
|
||||
|
||||
return expect(target).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should parse files from fileContentMap', () => {
|
||||
const indexContent = `
|
||||
syntax = "proto3";
|
||||
import "./unify_dependent.proto";
|
||||
|
||||
message Request {
|
||||
repeated string key1 = 1[(api.key) = 'f'];
|
||||
unify_dep3.Foo key2 = 2;
|
||||
}
|
||||
|
||||
service Example {
|
||||
option (api.uri_prefix) = "//example.com";
|
||||
rpc Biz1(Request) returns (unify_dep3.Response) {
|
||||
option (api.uri) = '/api/biz1';
|
||||
}
|
||||
}
|
||||
`;
|
||||
const dependentContent = `
|
||||
syntax = "proto3";
|
||||
|
||||
package unify_dep3;
|
||||
|
||||
message Foo {
|
||||
string f_key1 = 1;
|
||||
}
|
||||
|
||||
message Response {}
|
||||
`;
|
||||
const fileContentMap = {
|
||||
'unify_index.proto': indexContent,
|
||||
'unify_dependent.proto': dependentContent,
|
||||
};
|
||||
|
||||
const expected = {
|
||||
namespace: '',
|
||||
unifyNamespace: 'root',
|
||||
include: ['./unify_dependent.proto'],
|
||||
};
|
||||
|
||||
const document = t.parse(
|
||||
'unify_index.proto',
|
||||
{ cache: false },
|
||||
fileContentMap,
|
||||
);
|
||||
const target = {
|
||||
namespace: document.namespace,
|
||||
unifyNamespace: document.unifyNamespace,
|
||||
include: document.includes,
|
||||
};
|
||||
|
||||
return expect(target).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should parse files from fileContentMap with relative path', () => {
|
||||
const indexContent = `
|
||||
syntax = "proto3";
|
||||
import "unify_dependent.proto";
|
||||
|
||||
message Request {
|
||||
repeated string key1 = 1[(api.key) = 'f'];
|
||||
unify_dep3.Foo key2 = 2;
|
||||
}
|
||||
|
||||
service Example {
|
||||
option (api.uri_prefix) = "//example.com";
|
||||
rpc Biz1(Request) returns (unify_dep3.Response) {
|
||||
option (api.uri) = '/api/biz1';
|
||||
}
|
||||
}
|
||||
`;
|
||||
const dependentContent = `
|
||||
syntax = "proto3";
|
||||
|
||||
package unify_dep3;
|
||||
|
||||
message Foo {
|
||||
string f_key1 = 1;
|
||||
}
|
||||
|
||||
message Response {}
|
||||
`;
|
||||
const fileContentMap = {
|
||||
'relative/unify_index.proto': indexContent,
|
||||
'relative/unify_dependent.proto': dependentContent,
|
||||
};
|
||||
|
||||
const expected = {
|
||||
namespace: '',
|
||||
unifyNamespace: 'root',
|
||||
include: ['unify_dependent.proto'],
|
||||
};
|
||||
|
||||
const document = t.parse(
|
||||
'relative/unify_index.proto',
|
||||
{ cache: false },
|
||||
fileContentMap,
|
||||
);
|
||||
const target = {
|
||||
namespace: document.namespace,
|
||||
unifyNamespace: document.unifyNamespace,
|
||||
include: document.includes,
|
||||
};
|
||||
|
||||
return expect(target).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should parse files with cache', () => {
|
||||
const indexContent = `
|
||||
syntax = "proto3";
|
||||
message Foo {}
|
||||
`;
|
||||
const indexxContent = `
|
||||
syntax = "proto3";
|
||||
`;
|
||||
|
||||
const rootContent = `
|
||||
import "unify_index_cache.proto";
|
||||
syntax = "proto3";
|
||||
`;
|
||||
|
||||
t.parse(
|
||||
'unify_index_cache.proto',
|
||||
{
|
||||
cache: true,
|
||||
},
|
||||
{
|
||||
'unify_index_cache.proto': indexContent,
|
||||
},
|
||||
);
|
||||
|
||||
t.parse(
|
||||
'unify_root.proto',
|
||||
{ cache: false },
|
||||
{
|
||||
'unify_root.proto': rootContent,
|
||||
},
|
||||
);
|
||||
|
||||
const document = t.parse(
|
||||
'unify_index_cache.proto',
|
||||
{ cache: true },
|
||||
{
|
||||
'unify_index_cache.proto': indexxContent,
|
||||
},
|
||||
);
|
||||
|
||||
return expect(document.statements[0].name.value).to.eql('Foo');
|
||||
});
|
||||
|
||||
it('should parse files ignoring import', () => {
|
||||
const indexContent = `
|
||||
syntax = "proto3";
|
||||
import "google/protobuf/api.proto";
|
||||
`;
|
||||
|
||||
t.parse(
|
||||
'ignore.proto',
|
||||
{ cache: false },
|
||||
{
|
||||
'ignore.proto': indexContent,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should search files from searchPaths', () => {
|
||||
const idl = path.resolve(__dirname, 'idl/unify_search.proto');
|
||||
const depDir = path.resolve(__dirname, 'idl/dep');
|
||||
|
||||
const document = t.parse(idl, {
|
||||
cache: false,
|
||||
searchPaths: [depDir],
|
||||
});
|
||||
|
||||
const struct = document.statements[0] as t.InterfaceWithFields;
|
||||
return expect(struct.type).to.eql(t.SyntaxType.StructDefinition);
|
||||
});
|
||||
|
||||
it('should search files from relative searchPaths', () => {
|
||||
const document = t.parse('unify_search.proto', {
|
||||
root: path.resolve(__dirname, 'idl'),
|
||||
cache: false,
|
||||
searchPaths: ['./dep'],
|
||||
});
|
||||
|
||||
const struct = document.statements[0] as t.InterfaceWithFields;
|
||||
return expect(struct.type).to.eql(t.SyntaxType.StructDefinition);
|
||||
});
|
||||
|
||||
it('should parse files with a syntax error', () => {
|
||||
const indexContent = `
|
||||
syntax = "proto3";
|
||||
message Foo {
|
||||
`;
|
||||
|
||||
try {
|
||||
t.parse(
|
||||
'error.proto',
|
||||
{ cache: false },
|
||||
{
|
||||
'error.proto': indexContent,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
return expect(err.message).to.eql("illegal token 'null', '=' expected");
|
||||
}
|
||||
|
||||
return expect(0).to.eql(1);
|
||||
});
|
||||
|
||||
it('should parse files with a entry file error within fileContentMap', () => {
|
||||
try {
|
||||
t.parse('specify_error.proto', { cache: false }, {});
|
||||
} catch (err) {
|
||||
return expect(err.message).to.equal(
|
||||
'file "specify_error.proto" does not exist in fileContentMap',
|
||||
);
|
||||
}
|
||||
|
||||
return expect(0).to.eql(1);
|
||||
});
|
||||
|
||||
it('should parse files with a file error', () => {
|
||||
const indexContent = `
|
||||
syntax = "proto3";
|
||||
import "./dependent.proto";
|
||||
`;
|
||||
|
||||
try {
|
||||
t.parse(
|
||||
'error.proto',
|
||||
{ cache: false },
|
||||
{
|
||||
'error.proto': indexContent,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
return expect(err.message).to.eql(
|
||||
'file dependent.proto does not exist in fileContentMap',
|
||||
);
|
||||
}
|
||||
|
||||
return expect(0).to.eql(1);
|
||||
});
|
||||
|
||||
it('should parse files with a real file error', () => {
|
||||
try {
|
||||
t.parse('real_error.proto', { cache: false });
|
||||
} catch (err) {
|
||||
return expect(err.message).to.contain('no such file');
|
||||
}
|
||||
|
||||
return expect(0).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('all index', () => {
|
||||
it('should parse files with a file error', () => {
|
||||
try {
|
||||
t.parse('error', { cache: false });
|
||||
} catch (err) {
|
||||
return expect(err.message).to.eql('invalid filePath: "error"');
|
||||
}
|
||||
|
||||
return expect(0).to.eql(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user