Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions compiler/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,10 @@ import { writeFile, mkdir } from 'fs/promises'
import { join } from 'path'
import stringify from 'safe-stable-stringify'
import { Model } from './model/metamodel'
import {
compileEndpoints,
compileSpecification,
reAddAvailability
} from './model/build-model'
import buildJsonSpec, { JsonSpec } from './model/json-spec'
import { compileSpecification } from './model/build-model'
import { ValidationErrors } from './validation-errors'

type StepFunction = (model: Model, restSpec: Map<string, JsonSpec>, errors: ValidationErrors) => Promise<Model>
type StepFunction = (model: Model, errors: ValidationErrors) => Promise<Model>

/**
* The main job of the compiler is to generate the Model and write it on disk.
Expand All @@ -42,7 +37,6 @@ type StepFunction = (model: Model, restSpec: Map<string, JsonSpec>, errors: Vali
export default class Compiler {
queue: StepFunction[]
model: Model
jsonSpec: Map<string, JsonSpec>
errors: ValidationErrors
specsFolder: string
outputFolder: string
Expand All @@ -55,16 +49,13 @@ export default class Compiler {
}

generateModel (): this {
this.jsonSpec = buildJsonSpec()
const endpoints = compileEndpoints()
this.model = compileSpecification(endpoints, this.specsFolder, this.outputFolder)
this.model = reAddAvailability(this.model) // resync availability information based on json spec if typescript has none.
this.model = compileSpecification(this.specsFolder, this.outputFolder)
return this
}

async write (): Promise<void> {
for (const step of this.queue) {
this.model = await step(this.model, this.jsonSpec, this.errors)
this.model = await step(this.model, this.errors)
}

const customStringify = stringify.configure(
Expand Down
4 changes: 0 additions & 4 deletions compiler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ import { readFileSync, existsSync, lstatSync } from 'fs'
import { join, resolve } from 'path'
import { argv } from 'zx'
import Compiler from './compiler'
import validateRestSpec from './steps/validate-rest-spec'
import addInfo from './steps/add-info'
import addDescription from './steps/add-description'
import validateModel from './steps/validate-model'
import readDefinitionValidation from './steps/read-definition-validation'
import addDeprecation from './steps/add-deprecation'
Expand Down Expand Up @@ -73,8 +71,6 @@ compiler
.step(addInfo)
.step(addDeprecation)
.step(readDefinitionValidation)
.step(validateRestSpec)
.step(addDescription)
.step(validateModel)
.step(addExamples)
.write()
Expand Down
73 changes: 13 additions & 60 deletions compiler/src/model/build-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import {
TypeAliasDeclaration
} from 'ts-morph'
import * as model from './metamodel'
import buildJsonSpec from './json-spec'
import {
assert,
customTypes,
Expand All @@ -57,62 +56,10 @@ import {
mediaTypeToStringArray
} from './utils'

const jsonSpec = buildJsonSpec()

export function reAddAvailability (model: model.Model): model.Model {
for (const [api, spec] of jsonSpec.entries()) {
for (const endpoint of model.endpoints) {
if (endpoint.name === api) {
if ((spec.stability != null || spec.visibility != null) && (endpoint.availability.stack === undefined && endpoint.availability.serverless === undefined)) {
endpoint.availability = {
stack: {
stability: spec.stability,
visibility: spec.visibility
}
}
}
}
}
}
return model
}

export function compileEndpoints (): Record<string, model.Endpoint> {
// Create endpoints and merge them with
// the recorded mappings if present.
const map = {}
for (const [api, spec] of jsonSpec.entries()) {
map[api] = {
name: api,
description: spec.documentation.description,
docUrl: spec.documentation.url,
docTag: spec.docTag,
extDocUrl: spec.externalDocs?.url,
// Setting these values by default should be removed
// when we no longer use rest-api-spec stubs as the
// source of truth for stability/visibility.
availability: {},
request: null,
requestBodyRequired: Boolean(spec.body?.required),
response: null,
urls: spec.url.paths.map(path => {
return {
path: path.path,
methods: path.methods,
...(path.deprecated != null && { deprecation: path.deprecated })
}
})
}
if (typeof spec.feature_flag === 'string') {
map[api].availability.stack = { featureFlag: spec.feature_flag }
}
}
return map
}

export function compileSpecification (endpointMappings: Record<string, model.Endpoint>, specsFolder: string, outputFolder: string): model.Model {
export function compileSpecification (specsFolder: string, outputFolder: string): model.Model {
const tsConfigFilePath = join(specsFolder, 'tsconfig.json')
const project = new Project({ tsConfigFilePath })
const endpointMappings: Record<string, model.Endpoint> = {}

verifyUniqueness(project)

Expand Down Expand Up @@ -151,9 +98,6 @@ export function compileSpecification (endpointMappings: Record<string, model.End
definedButNeverUsed.join('\n'),
{ encoding: 'utf8', flag: 'w' }
)
for (const api of jsonSpec.keys()) {
model.endpoints.push(endpointMappings[api])
}

// Visit all class, interface, enum and type alias definitions
for (const declaration of declarations.classes) {
Expand All @@ -175,6 +119,11 @@ export function compileSpecification (endpointMappings: Record<string, model.End
// Sort the types in alphabetical order
sortTypeDefinitions(model.types)

const sortedEndpointKeys = Object.keys(endpointMappings).sort()
for (const key of sortedEndpointKeys) {
model.endpoints.push(endpointMappings[key])
}

return model
}

Expand Down Expand Up @@ -225,6 +174,10 @@ function compileClassOrInterfaceDeclaration (declaration: ClassDeclaration | Int
throw new Error(`Cannot find url template for ${namespace}, very likely the specification folder does not follow the rest-api-spec`)
}

if (type.description !== '' && type.description !== null && type.description !== undefined) {
mapping.description = type.description
}

let pathMember: Node | null = null
let bodyProperties: model.Property[] = []
let bodyValue: model.ValueOf | null = null
Expand Down Expand Up @@ -288,7 +241,7 @@ function compileClassOrInterfaceDeclaration (declaration: ClassDeclaration | Int
assert(
pathMember as Node,
urlTemplateParams.includes(part.name),
`The property '${part.name}' does not exist in the rest-api-spec ${namespace} url template`
`The property '${part.name}' does not exist in the url template`
)
if (type.query.map(p => p.name).includes(part.name)) {
const queryType = type.query.find(property => property != null && property.name === part.name) as model.Property
Expand Down Expand Up @@ -636,7 +589,7 @@ function visitRequestOrResponseProperty (member: PropertyDeclaration | PropertyS
* ```
* urls: [
* {
* /** @deprecated 1.2.3 Use something else
* /** \@deprecated 1.2.3 Use something else
* path: '/some/path',
* methods: ["GET", "POST"]
* }
Expand Down
84 changes: 0 additions & 84 deletions compiler/src/model/json-spec.ts

This file was deleted.

22 changes: 18 additions & 4 deletions compiler/src/model/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import {
Node,
Project
} from 'ts-morph'
import { closest } from 'fastest-levenshtein'
import semver from 'semver'
import chalk from 'chalk'
import * as model from './metamodel'
Expand Down Expand Up @@ -629,6 +628,23 @@ function setTags<TType extends model.BaseType | model.Property | model.EnumMembe
}
}

export function updateEndpoints (mappings: Record<string, model.Endpoint>, name: string): model.Endpoint {
mappings[name] = {
name: name,
// @ts-expect-error TODO
description: null,
// @ts-expect-error TODO
docUrl: null,
request: null,
requestBodyRequired: false,
response: null,
urls: []
}
mappings[name].availability = {}

return mappings[name]
}

/** Lifts jsDoc type annotations to request properties */
export function hoistRequestAnnotations (
request: model.Request, jsDocs: JSDoc[], mappings: Record<string, model.Endpoint>, response: model.TypeName | null
Expand All @@ -651,9 +667,7 @@ export function hoistRequestAnnotations (
assert(jsDocs, apiName !== '' && apiName !== null && apiName !== undefined,
`Request ${request.name.name} does not declare the @rest_spec_name to link back to`)

const endpoint = mappings[apiName]
assert(jsDocs, endpoint != null, `The api '${apiName}' does not exists, did you mean '${closest(apiName, Object.keys(mappings))}'?`)

const endpoint = updateEndpoints(mappings, apiName)
endpoint.request = request.name
endpoint.response = response

Expand Down
3 changes: 1 addition & 2 deletions compiler/src/steps/add-deprecation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@
*/

import * as model from '../model/metamodel'
import { JsonSpec } from '../model/json-spec'

/**
* Populates the `deprecation` field for endpoints from the value of the corresponding request definition.
*/
export default async function addContentType (model: model.Model, jsonSpec: Map<string, JsonSpec>): Promise<model.Model> {
export default async function addContentType (model: model.Model): Promise<model.Model> {
for (const endpoint of model.endpoints) {
if (endpoint.deprecation != null) {
continue
Expand Down
56 changes: 0 additions & 56 deletions compiler/src/steps/add-description.ts

This file was deleted.

Loading
Loading