Skip to content

Commit 02cfb84

Browse files
authored
Add parameters to compiler for specifying input and output
1 parent d5e725a commit 02cfb84

File tree

19 files changed

+133
-24
lines changed

19 files changed

+133
-24
lines changed

.github/workflows/gh-pages-report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
2828
- name: Generate output
2929
run: |
30-
npm run generate-schema --prefix compiler
30+
make generate
3131
3232
- name: Generate graph
3333
run: |

.github/workflows/output-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
2323
- name: Generate output
2424
run: |
25-
npm run generate-schema --prefix compiler
25+
make generate
2626
npm run start --prefix typescript-generator
2727
2828
- name: Validate TS output

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,6 @@ output/schema/routes.go
6262

6363
# Editor lockfiles
6464
.*.swp
65+
66+
# Test suite outputs
67+
compiler/test/**/output/

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ validate-no-cache: ## Validate a given endpoint request or response without loca
88

99
generate: ## Generate the output spec
1010
@echo ">> generating the spec .."
11-
@npm run generate-schema --prefix compiler
11+
@npm run generate-schema --prefix compiler -- --spec ../specification/ --output ../output/
1212
@npm run start --prefix typescript-generator
1313

1414
compile: ## Compile the specification

compiler/src/compiler.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* under the License.
1818
*/
1919

20-
import { writeFile } from 'fs/promises'
20+
import { writeFile, mkdir } from 'fs/promises'
2121
import { join } from 'path'
2222
import stringify from 'safe-stable-stringify'
2323
import { Model } from './model/metamodel'
@@ -41,17 +41,19 @@ export default class Compiler {
4141
jsonSpec: Map<string, JsonSpec>
4242
errors: ValidationErrors
4343
specsFolder: string
44+
outputFolder: string
4445

45-
constructor (specsFolder: string) {
46+
constructor (specsFolder: string, outputFolder: string) {
4647
this.queue = []
4748
this.errors = new ValidationErrors()
4849
this.specsFolder = specsFolder
50+
this.outputFolder = outputFolder
4951
}
5052

5153
generateModel (): this {
5254
this.jsonSpec = buildJsonSpec()
5355
const endpoints = compileEndpoints()
54-
this.model = compileSpecification(endpoints, this.specsFolder)
56+
this.model = compileSpecification(endpoints, this.specsFolder, this.outputFolder)
5557
return this
5658
}
5759

@@ -60,16 +62,17 @@ export default class Compiler {
6062
this.model = await step(this.model, this.jsonSpec, this.errors)
6163
}
6264

65+
await mkdir(join(this.outputFolder, 'schema'), { recursive: true })
6366
await writeFile(
64-
join(__dirname, '..', '..', 'output', 'schema', 'schema.json'),
67+
join(this.outputFolder, 'schema', 'schema.json'),
6568
stringify(this.model, null, 2),
6669
'utf8'
6770
)
6871

6972
this.errors.log()
7073

7174
await writeFile(
72-
join(__dirname, '..', '..', 'output', 'schema', 'validation-errors.json'),
75+
join(this.outputFolder, 'schema', 'validation-errors.json'),
7376
stringify(this.errors, null, 2),
7477
'utf8'
7578
)

compiler/src/index.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
* under the License.
1818
*/
1919

20-
import { readFileSync } from 'fs'
21-
import { join } from 'path'
20+
import { readFileSync, existsSync, lstatSync } from 'fs'
21+
import { join, resolve } from 'path'
22+
import { argv } from 'zx'
2223
import Compiler from './compiler'
2324
import validateRestSpec from './steps/validate-rest-spec'
2425
import addInfo from './steps/add-info'
@@ -36,7 +37,32 @@ if (nodejsMajor !== nvmMajor) {
3637
process.exit(1)
3738
}
3839

39-
const compiler = new Compiler(join(__dirname, '..', '..', 'specification'))
40+
let specsFolder: string|undefined = argv.spec
41+
if (specsFolder !== '' && specsFolder !== undefined) {
42+
// We were given a specification, let's make sure it's a directory.
43+
specsFolder = resolve(specsFolder)
44+
45+
// lstatSync raises an error if there's nothing at the path so we
46+
// check that something exists there first before checking it's a directory.
47+
const specFolderIsDirectory: boolean = (existsSync(specsFolder) && lstatSync(specsFolder).isDirectory())
48+
if (!specFolderIsDirectory) {
49+
console.error(`The specification specified at '${specsFolder}' wasn't a valid directory`)
50+
process.exit(1)
51+
}
52+
// User didn't specify a spec location so we ask for one.
53+
} else {
54+
console.error('--spec must be specified and be a valid directory path')
55+
process.exit(1)
56+
}
57+
58+
// It's okay if the output folder doesn't exist initially.
59+
const outputFolder: string|undefined = argv.output
60+
if (outputFolder === '' || outputFolder === undefined) {
61+
console.error('--output must be specified')
62+
process.exit(1)
63+
}
64+
65+
const compiler = new Compiler(specsFolder, outputFolder)
4066

4167
compiler
4268
.generateModel()

compiler/src/model/build-model.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* under the License.
1818
*/
1919

20-
import { writeFileSync } from 'fs'
20+
import { mkdirSync, writeFileSync } from 'fs'
2121
import { join } from 'path'
2222
import { STATUS_CODES } from 'http'
2323
import {
@@ -88,7 +88,7 @@ export function compileEndpoints (): Record<string, model.Endpoint> {
8888
return map
8989
}
9090

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

@@ -123,10 +123,11 @@ export function compileSpecification (endpointMappings: Record<string, model.End
123123
}
124124
}
125125

126+
mkdirSync(join(outputFolder, 'dangling-types'), { recursive: true })
126127
writeFileSync(
127-
join(__dirname, '..', '..', '..', 'output', 'dangling-types', 'dangling.csv'),
128+
join(outputFolder, 'dangling-types', 'dangling.csv'),
128129
definedButNeverUsed.join('\n'),
129-
'utf8'
130+
{ encoding: 'utf8', flag: 'w' }
130131
)
131132
for (const api of jsonSpec.keys()) {
132133
model.endpoints.push(endpointMappings[api])

compiler/test/body-codegen-name/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import Compiler from '../../src/compiler'
2323
import * as Model from '../../src/model/metamodel'
2424

2525
const specsFolder = join(__dirname, 'specification')
26+
const outputFolder = join(__dirname, 'output')
2627

2728
test('type reference body must have a @codegen_name', t => {
28-
const compiler = new Compiler(specsFolder)
29+
const compiler = new Compiler(specsFolder, outputFolder)
2930
const error = t.throws(() => compiler.generateModel())
3031
t.is(error?.message, 'You should configure a body @codegen_name')
3132
})

compiler/test/duplicate-body-codegen-name/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import Compiler from '../../src/compiler'
2323
import * as Model from '../../src/model/metamodel'
2424

2525
const specsFolder = join(__dirname, 'specification')
26+
const outputFolder = join(__dirname, 'output')
2627

2728
test('@codegen_name cannot already exist elsewhere', t => {
28-
const compiler = new Compiler(specsFolder)
29+
const compiler = new Compiler(specsFolder, outputFolder)
2930
const error = t.throws(() => compiler.generateModel())
3031
t.is(error?.message, "The codegen_name 'id' already exists as a property in the path or query.")
3132
})

compiler/test/no-body/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import Compiler from '../../src/compiler'
2323
import * as Model from '../../src/model/metamodel'
2424

2525
const specsFolder = join(__dirname, 'specification')
26+
const outputFolder = join(__dirname, 'output')
2627

2728
test("Body cannot be defined if the API methods don't allow it", t => {
28-
const compiler = new Compiler(specsFolder)
29+
const compiler = new Compiler(specsFolder, outputFolder)
2930
const error = t.throws(() => compiler.generateModel())
3031
t.is(error?.message, "_global.info.Request can't have a body, allowed methods: GET")
3132
})

compiler/test/request-fields/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import Compiler from '../../src/compiler'
2323
import * as Model from '../../src/model/metamodel'
2424

2525
const specsFolder = join(__dirname, 'specification')
26+
const outputFolder = join(__dirname, 'output')
2627

2728
test('Request definition cannot have unknown properties', t => {
28-
const compiler = new Compiler(specsFolder)
29+
const compiler = new Compiler(specsFolder, outputFolder)
2930
const error = t.throws(() => compiler.generateModel())
3031
t.is(error?.message, 'Unknown request property: headers')
3132
})

compiler/test/response-exceptions-fields/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import Compiler from '../../src/compiler'
2323
import * as Model from '../../src/model/metamodel'
2424

2525
const specsFolder = join(__dirname, 'specification')
26+
const outputFolder = join(__dirname, 'output')
2627

2728
test('Response exceptions definition cannot have unknown properties', t => {
28-
const compiler = new Compiler(specsFolder)
29+
const compiler = new Compiler(specsFolder, outputFolder)
2930
const error = t.throws(() => compiler.generateModel())
3031
t.is(error?.message, 'Exception.body and Exception.statusCode are the only Exception properties supported')
3132
})

compiler/test/response-fields/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import Compiler from '../../src/compiler'
2323
import * as Model from '../../src/model/metamodel'
2424

2525
const specsFolder = join(__dirname, 'specification')
26+
const outputFolder = join(__dirname, 'output')
2627

2728
test('Response definition cannot have unknown properties', t => {
28-
const compiler = new Compiler(specsFolder)
29+
const compiler = new Compiler(specsFolder, outputFolder)
2930
const error = t.throws(() => compiler.generateModel())
3031
t.is(error?.message, 'Response.body and Response.exceptions are the only Response properties supported')
3132
})

compiler/test/types/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import Compiler from '../../src/compiler'
2323
import * as Model from '../../src/model/metamodel'
2424

2525
const specsFolder = join(__dirname, 'specification')
26-
const compiler = new Compiler(specsFolder)
26+
const outputFolder = join(__dirname, 'output')
27+
const compiler = new Compiler(specsFolder, outputFolder)
2728
const { model } = compiler.generateModel()
2829

2930
test('interfaces', t => {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
/**
21+
* @rest_spec_name index
22+
* @since 0.0.0
23+
* @stability stable
24+
*/
25+
export interface Request {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"extends": "../../../../specification/tsconfig.json",
3+
"typeRoots": ["./**/*.ts"],
4+
"include": ["./**/*.ts"]
5+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { join } from 'path'
21+
import { existsSync, lstatSync, rmSync } from 'fs'
22+
import test from 'ava'
23+
import Compiler from '../../src/compiler'
24+
import * as Model from '../../src/model/metamodel'
25+
26+
const specsFolder = join(__dirname, 'specification')
27+
const outputFolder = join(__dirname, 'output')
28+
29+
test('compiler creates schema directory at outputFolder', async t => {
30+
rmSync(outputFolder, {recursive: true, force: true});
31+
32+
const compiler = new Compiler(specsFolder, outputFolder)
33+
await compiler.generateModel().write();
34+
35+
const schemaFolder = join(outputFolder, 'schema')
36+
t.true(existsSync(schemaFolder) && lstatSync(schemaFolder).isDirectory());
37+
})

compiler/test/wrong-api-name/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import Compiler from '../../src/compiler'
2323
import * as Model from '../../src/model/metamodel'
2424

2525
const specsFolder = join(__dirname, 'specification')
26+
const outputFolder = join(__dirname, 'output')
2627

2728
test('Wrong rest_spec_name for request definition', t => {
28-
const compiler = new Compiler(specsFolder)
29+
const compiler = new Compiler(specsFolder, outputFolder)
2930
const error = t.throws(() => compiler.generateModel())
3031
t.true(error?.message.startsWith("The api 'foobar' does not exists, did you mean"))
3132
})

compiler/test/wrong-namespace/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import Compiler from '../../src/compiler'
2323
import * as Model from '../../src/model/metamodel'
2424

2525
const specsFolder = join(__dirname, 'specification')
26+
const outputFolder = join(__dirname, 'output')
2627

2728
test('Wrong namespace for request definition', t => {
28-
const compiler = new Compiler(specsFolder)
29+
const compiler = new Compiler(specsFolder, outputFolder)
2930
const error = t.throws(() => compiler.generateModel())
3031
t.is(error?.message, 'Cannot find url template for _global.foobar, very likely the specification folder does not follow the rest-api-spec')
3132
})

0 commit comments

Comments
 (0)