Skip to content

Commit 209f03b

Browse files
authored
Encoded specification uniqueness rules (elastic#403)
1 parent 2651191 commit 209f03b

File tree

11 files changed

+329
-267
lines changed

11 files changed

+329
-267
lines changed

compiler/model/utils.ts

+105-26
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import semver from 'semver'
3636
import chalk from 'chalk'
3737
import * as model from './metamodel'
3838
import { EOL } from 'os'
39-
import { dirname } from 'path'
39+
import { dirname, sep } from 'path'
4040

4141
/**
4242
* Behaviors that the compiler recognized
@@ -901,51 +901,130 @@ export function assert (node: Node | Node[] | undefined, condition: boolean, mes
901901
export function verifyUniqueness (project: Project): void {
902902
const types: Map<string, string[]> = new Map()
903903
const common: string[] = []
904+
905+
// get global `_types`
906+
for (const sourceFile of project.getSourceFiles()) {
907+
const path = dirname(sourceFile.getFilePath().replace(/.*[/\\]specification[/\\]?/, ''))
908+
if (!path.startsWith('_types')) continue
909+
910+
for (const declaration of sourceFile.getClasses()) {
911+
const name = declaration.getName()
912+
assert(declaration, name != null, 'Anonymous classes cannot exists')
913+
assert(declaration, !name.endsWith('Request') && !name.endsWith('Response'), 'A type cannot end with "Request" or "Response"')
914+
assert(declaration, !common.includes(name), `${name} is already defined in the global types`)
915+
common.push(name)
916+
}
917+
918+
for (const declaration of sourceFile.getInterfaces()) {
919+
assert(declaration, !declaration.getName().endsWith('Request') && !declaration.getName().endsWith('Response'), 'A type cannot end with "Request" or "Response"')
920+
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined in the global types`)
921+
common.push(declaration.getName())
922+
}
923+
924+
for (const declaration of sourceFile.getEnums()) {
925+
assert(declaration, !declaration.getName().endsWith('Request') && !declaration.getName().endsWith('Response'), 'A type cannot end with "Request" or "Response"')
926+
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined in the global types`)
927+
common.push(declaration.getName())
928+
}
929+
930+
for (const declaration of sourceFile.getTypeAliases()) {
931+
assert(declaration, !declaration.getName().endsWith('Request') && !declaration.getName().endsWith('Response'), 'A type cannot end with "Request" or "Response"')
932+
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined in the global types`)
933+
common.push(declaration.getName())
934+
}
935+
}
936+
937+
// nested `_types`
938+
for (const sourceFile of project.getSourceFiles()) {
939+
const path = dirname(sourceFile.getFilePath().replace(/.*[/\\]specification[/\\]?/, ''))
940+
if (path.startsWith('_types')) continue
941+
if (!path.includes('_types')) continue
942+
943+
const namespace = path.startsWith('_global') ? `_global${sep}${path.split(sep)[1]}` : path.split(sep)[0]
944+
const names = types.get(namespace) ?? []
945+
946+
for (const declaration of sourceFile.getClasses()) {
947+
const name = declaration.getName()
948+
assert(declaration, name != null, 'Anonymous classes cannot exists')
949+
assert(declaration, !name.endsWith('Request') && !name.endsWith('Response'), 'A type cannot end with "Request" or "Response"')
950+
assert(declaration, !common.includes(name), `${name} is already defined in ${namespace} is already defined in the global types`)
951+
assert(declaration, !names.includes(name), `${name} is already defined in ${namespace} local types`)
952+
names.push(name)
953+
}
954+
955+
for (const declaration of sourceFile.getInterfaces()) {
956+
assert(declaration, !declaration.getName().endsWith('Request') && !declaration.getName().endsWith('Response'), 'A type cannot end with "Request" or "Response"')
957+
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined in the global types`)
958+
assert(declaration, !names.includes(declaration.getName()), `${declaration.getName()} is already defined in ${namespace} local types`)
959+
names.push(declaration.getName())
960+
}
961+
962+
for (const declaration of sourceFile.getEnums()) {
963+
assert(declaration, !declaration.getName().endsWith('Request') && !declaration.getName().endsWith('Response'), 'A type cannot end with "Request" or "Response"')
964+
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined in the global types`)
965+
assert(declaration, !names.includes(declaration.getName()), `${declaration.getName()} is already defined in ${namespace} local types`)
966+
names.push(declaration.getName())
967+
}
968+
969+
for (const declaration of sourceFile.getTypeAliases()) {
970+
assert(declaration, !declaration.getName().endsWith('Request') && !declaration.getName().endsWith('Response'), 'A type cannot end with "Request" or "Response"')
971+
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined in the global types`)
972+
assert(declaration, !names.includes(declaration.getName()), `${declaration.getName()} is already defined in ${namespace} local types`)
973+
names.push(declaration.getName())
974+
}
975+
976+
types.set(namespace, names)
977+
}
978+
979+
// every other namespaced type
904980
for (const sourceFile of project.getSourceFiles()) {
905981
const path = dirname(sourceFile.getFilePath().replace(/.*[/\\]specification[/\\]?/, ''))
906-
const isCommon = path.startsWith('_types')
982+
if (path.includes('_types')) continue
983+
984+
const namespace = path.startsWith('_global') ? `_global${sep}${path.split(sep)[1]}` : path.split(sep)[0]
907985
const names = types.get(path) ?? []
986+
const localTypes = types.get(namespace) ?? []
908987

909988
for (const declaration of sourceFile.getClasses()) {
910989
const name = declaration.getName()
911990
assert(declaration, name != null, 'Anonymous classes cannot exists')
912-
assert(declaration, !names.includes(name), `${name} is already defined`)
913-
assert(declaration, !common.includes(name), `${name} is already defined inside`)
914-
if (isCommon) {
915-
common.push(name)
916-
} else {
917-
names.push(name)
991+
if (name !== 'Request' && name !== 'Response') {
992+
assert(declaration, !name.endsWith('Request') && !name.endsWith('Response'), 'A type cannot end with "Request" or "Response"')
918993
}
994+
assert(declaration, !names.includes(name), `${name} is already defined`)
995+
assert(declaration, !common.includes(name), `${name} is already defined in the global types`)
996+
assert(declaration, !localTypes.includes(name), `${name} is already defined in ${namespace} local types`)
997+
names.push(name)
919998
}
920999

9211000
for (const declaration of sourceFile.getInterfaces()) {
922-
assert(declaration, !names.includes(declaration.getName()), `${declaration.getName()} is already defined`)
923-
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined`)
924-
if (isCommon) {
925-
common.push(declaration.getName())
926-
} else {
927-
names.push(declaration.getName())
1001+
if (declaration.getName() !== 'Request' && declaration.getName() !== 'Response') {
1002+
assert(declaration, !declaration.getName().endsWith('Request') && !declaration.getName().endsWith('Response'), 'A type cannot end with "Request" or "Response"')
9281003
}
1004+
assert(declaration, !names.includes(declaration.getName()), `${declaration.getName()} is already defined`)
1005+
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined in the global types`)
1006+
assert(declaration, !localTypes.includes(declaration.getName()), `${declaration.getName()} is already defined in ${namespace} local types`)
1007+
names.push(declaration.getName())
9291008
}
9301009

9311010
for (const declaration of sourceFile.getEnums()) {
932-
assert(declaration, !names.includes(declaration.getName()), `${declaration.getName()} is already defined`)
933-
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined`)
934-
if (isCommon) {
935-
common.push(declaration.getName())
936-
} else {
937-
names.push(declaration.getName())
1011+
if (declaration.getName() !== 'Request' && declaration.getName() !== 'Response') {
1012+
assert(declaration, !declaration.getName().endsWith('Request') && !declaration.getName().endsWith('Response'), 'A type cannot end with "Request" or "Response"')
9381013
}
1014+
assert(declaration, !names.includes(declaration.getName()), `${declaration.getName()} is already defined`)
1015+
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined in the global types`)
1016+
assert(declaration, !localTypes.includes(declaration.getName()), `${declaration.getName()} is already defined in ${namespace} local types`)
1017+
names.push(declaration.getName())
9391018
}
9401019

9411020
for (const declaration of sourceFile.getTypeAliases()) {
942-
assert(declaration, !names.includes(declaration.getName()), `${declaration.getName()} is already defined`)
943-
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined`)
944-
if (isCommon) {
945-
common.push(declaration.getName())
946-
} else {
947-
names.push(declaration.getName())
1021+
if (declaration.getName() !== 'Request' && declaration.getName() !== 'Response') {
1022+
assert(declaration, !declaration.getName().endsWith('Request') && !declaration.getName().endsWith('Response'), 'A type cannot end with "Request" or "Response"')
9481023
}
1024+
assert(declaration, !names.includes(declaration.getName()), `${declaration.getName()} is already defined`)
1025+
assert(declaration, !common.includes(declaration.getName()), `${declaration.getName()} is already defined in the global types`)
1026+
assert(declaration, !localTypes.includes(declaration.getName()), `${declaration.getName()} is already defined in ${namespace} local types`)
1027+
names.push(declaration.getName())
9491028
}
9501029

9511030
types.set(path, names)

compiler/steps/validate-model.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
169169
}
170170

171171
// ErrorResponse is not referenced anywhere, but any API could return it if an error happens.
172-
validateTypeRef({ namespace: '_types', name: 'ErrorResponse' }, undefined, new Set())
172+
validateTypeRef({ namespace: '_types', name: 'ErrorResponseBase' }, undefined, new Set())
173173

174174
// ----- Alright, let's go!
175175

docs/specification-structure.md

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Types defined inside the top level `_types` folder should be globally
2424
unique specification-wide, while types defined inside namespaces in `*/_types`
2525
should be globally unique within the namespace where they are defined.
2626

27+
Unless you are defining a request or response type, a type name cannot
28+
end with `Request` or `Response`.
29+
2730
### Request and Response definitions
2831

2932
Request and Reponse definitions should be placed by structly following

0 commit comments

Comments
 (0)