in compiler/src/transform/filter-by-availability.ts [26:252]
function filterModel (inputModel: Model, stack: boolean, serverless: boolean, visibility: string[]): Model {
// filter over visibility only exclude if present and not matching, include by default.
function includeVisibility (localVis: Visibility | undefined): boolean {
if (localVis === undefined || visibility === undefined) {
return true
}
return visibility.includes(localVis)
}
// filter used against the provided availability
// used to filter out endpoints and as a filter for items with availability (Enum & Property).
function include (availabilities: Availabilities): boolean {
if ((availabilities.stack !== undefined) && stack) {
return includeVisibility(availabilities.stack.visibility)
}
if ((availabilities.serverless !== undefined) && serverless) {
return includeVisibility(availabilities.serverless.visibility)
}
return false
}
// used to filter out individual items within types.
function filterItem () {
return (item) => {
return (item.availability !== undefined) ? include(item.availability) : true
}
}
// short comparison for two TypeNames
function cmpTypeNames (t1, t2: TypeName): boolean {
return t1.name === t2.name && t1.namespace === t2.namespace
}
// Returns the fully-qualified name of a type name
function fqn (name: TypeName): string {
return `${name.namespace}:${name.name}`
}
// return early if the type already has been added
// fetches the original type from the input model
// save its presence to prevent recursion and doubles
// continues down the type tree for any new types
function addTypeToOutput (typeName: TypeName): void {
if (seen.has(fqn(typeName))) {
return
}
inputModel.types.forEach((typeDef) => {
if (cmpTypeNames(typeName, typeDef.name)) {
// add the TypeDefinition to the output
output.types.push(typeDef)
// store the type infos to prevent doubles & recursive calls
seen.add(fqn(typeName))
// first time seeing this type so we explore the type
exploreTypedef(typeDef)
}
})
}
// handles the basic type field we can find
// user_defined_value and literal_value are omitted.
function addValueOf (item: ValueOf): void {
switch (item.kind) {
case 'instance_of':
addTypeToOutput(item.type)
break
case 'array_of':
addValueOf(item.value)
break
case 'union_of':
item.items.forEach((member) => {
addValueOf(member)
})
break
case 'dictionary_of':
addValueOf(item.key)
addValueOf(item.value)
break
}
}
function exploreTypedef (typeDef: TypeDefinition): void {
// handle generics
// not really useful for everyone, still useful for type_alias
if (typeDef.kind === 'interface' || typeDef.kind === 'request' || typeDef.kind === 'response' || typeDef.kind === 'type_alias') {
typeDef.generics?.forEach((generic) => {
addTypeToOutput(generic)
})
}
// handle behaviors
if (typeDef.kind === 'interface' || typeDef.kind === 'request' || typeDef.kind === 'response') {
typeDef.behaviors?.forEach((behavior) => {
addTypeToOutput(behavior.type)
behavior.generics?.forEach((generic) => {
addValueOf(generic)
})
})
}
// handle inherits & implements
if (typeDef.kind === 'interface' || typeDef.kind === 'request') {
if (typeDef.inherits !== undefined) {
addTypeToOutput(typeDef.inherits.type)
}
}
// handle body value and body properties for request and response
if (typeDef.kind === 'request' || typeDef.kind === 'response') {
switch (typeDef.body.kind) {
case 'value':
addValueOf(typeDef.body.value)
break
case 'properties':
typeDef.body.properties.forEach((property) => {
addValueOf(property.type)
})
break
}
}
// left over specific cases
switch (typeDef.kind) {
case 'interface':
typeDef.properties.forEach((property) => {
addValueOf(property.type)
})
break
case 'request':
typeDef.path.forEach((path) => {
addValueOf(path.type)
})
typeDef.query.forEach((query) => {
addValueOf(query.type)
})
break
case 'type_alias':
addValueOf(typeDef.type)
break
}
}
const seen = new Set<string>()
const output: Model = {
_info: inputModel._info,
types: new Array<TypeDefinition>(),
endpoints: new Array<Endpoint>()
}
const typeDefByName = new Map<string, TypeDefinition>()
for (const type of inputModel.types) {
typeDefByName.set(fqn(type.name), type)
}
// we filter to include only the matching endpoints
inputModel.endpoints.forEach((endpoint) => {
if (include(endpoint.availability)) {
// add the current endpoint
output.endpoints.push(endpoint)
if (endpoint.request !== null) {
const requestType = typeDefByName.get(fqn(endpoint.request))
if (requestType !== undefined) output.types.push(requestType)
}
if (endpoint.response !== null) {
const responseType = typeDefByName.get(fqn(endpoint.response))
if (responseType !== undefined) output.types.push(responseType)
}
}
})
// filter type items (properties / enum members)
inputModel.types.forEach((typeDef) => {
switch (typeDef.kind) {
case 'interface':
typeDef.properties = typeDef.properties.filter(filterItem())
break
case 'enum':
typeDef.members = typeDef.members.filter(filterItem())
addTypeToOutput(typeDef.name)
break
case 'request':
output.endpoints.forEach((endpoint) => {
if (endpoint.request?.name === typeDef.name.name && endpoint.request.namespace === typeDef.name.namespace) {
typeDef.path = typeDef.path.filter(filterItem())
typeDef.query = typeDef.query.filter(filterItem())
// filter out body properties
switch (typeDef.body.kind) {
case 'properties':
typeDef.body.properties = typeDef.body.properties.filter(filterItem())
break
}
}
})
break
case 'response':
output.endpoints.forEach((endpoint) => {
if (endpoint.response?.name === typeDef.name.name && endpoint.response.namespace === typeDef.name.namespace) {
// filter out body properties
switch (typeDef.body.kind) {
case 'properties':
typeDef.body.properties = typeDef.body.properties.filter(filterItem())
break
}
}
})
break
case 'type_alias':
addTypeToOutput(typeDef.name)
}
})
// we complete the spec with the missing types for the tree until exhaustion
output.types.forEach((typeDef) => {
exploreTypedef(typeDef)
})
return output
}