Skip to content

Commit

Permalink
Merge pull request #1414 from cucumber/json-schema
Browse files Browse the repository at this point in the history
Json schema for message protocol
  • Loading branch information
aslakhellesoy authored Apr 29, 2021
2 parents 5d65a05 + da67368 commit 065d679
Show file tree
Hide file tree
Showing 34 changed files with 475 additions and 486 deletions.
2 changes: 1 addition & 1 deletion javascript/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ clean-messages:

features/%.ndjson: features/%.feature
mkdir -p $(@D)
./scripts/fake-cucumber.sh --format ndjson $< > $@
./scripts/fake-cucumber.sh $< > $@
8 changes: 7 additions & 1 deletion javascript/default.mk
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ default: .tested
.codegen:
touch $@

.tested: .tested-npm
.tested: .tested-npm .built

.built: $(TYPESCRIPT_SOURCE_FILES) .codegen
pushd ../.. && \
npm run build && \
popd && \
touch $@

.tested-npm: $(TYPESCRIPT_SOURCE_FILES) .codegen
npm run test
Expand Down
8 changes: 4 additions & 4 deletions javascript/src/EmptyPickleTestStep.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { messages } from '@cucumber/messages'
import * as messages from '@cucumber/messages'
import TestStep from './TestStep'

export default class EmptyPickleTestStep extends TestStep {
public toMessage(): messages.TestCase.ITestStep {
return new messages.TestCase.TestStep({
public toMessage(): messages.TestStep {
return {
id: this.id,
})
}
}
}
2 changes: 1 addition & 1 deletion javascript/src/ErrorMessageGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import StackUtils from 'stack-utils'

export type MakeErrorMessage = (error: Error, sourceFrames: ReadonlyArray<string>) => string
export type MakeErrorMessage = (error: Error, sourceFrames: readonly string[]) => string

export function withFullStackTrace(): MakeErrorMessage {
const stack = new StackUtils({
Expand Down
28 changes: 13 additions & 15 deletions javascript/src/ExpressionStepDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import SupportCodeExecutor from './SupportCodeExecutor'
import { CucumberExpression, Expression, RegularExpression } from '@cucumber/cucumber-expressions'
import { messages } from '@cucumber/messages'
import * as messages from '@cucumber/messages'
import { AnyBody, IStepDefinition, ISupportCodeExecutor } from './types'

export default class ExpressionStepDefinition implements IStepDefinition {
constructor(
private readonly id: string,
private readonly expression: Expression,
private readonly sourceReference: messages.ISourceReference,
private readonly sourceReference: messages.SourceReference,
private readonly body: AnyBody
) {}

public match(pickleStep: messages.Pickle.IPickleStep): ISupportCodeExecutor | null {
public match(pickleStep: messages.PickleStep): ISupportCodeExecutor | null {
const expressionArgs = this.expression.match(pickleStep.text)
return expressionArgs === null
? null
Expand All @@ -24,26 +24,24 @@ export default class ExpressionStepDefinition implements IStepDefinition {
)
}

public toMessage(): messages.IEnvelope {
return new messages.Envelope({
stepDefinition: new messages.StepDefinition({
public toMessage(): messages.Envelope {
return {
stepDefinition: {
id: this.id,
pattern: new messages.StepDefinition.StepDefinitionPattern({
pattern: {
type: this.expressionType(),
source: this.expression.source,
}),
},
sourceReference: this.sourceReference,
}),
})
},
}
}

private expressionType(): messages.StepDefinition.StepDefinitionPattern.StepDefinitionPatternType {
private expressionType(): messages.StepDefinitionPatternType {
if (this.expression instanceof CucumberExpression) {
return messages.StepDefinition.StepDefinitionPattern.StepDefinitionPatternType
.CUCUMBER_EXPRESSION
return messages.StepDefinitionPatternType.CUCUMBER_EXPRESSION
} else if (this.expression instanceof RegularExpression) {
return messages.StepDefinition.StepDefinitionPattern.StepDefinitionPatternType
.REGULAR_EXPRESSION
return messages.StepDefinitionPatternType.REGULAR_EXPRESSION
} else {
throw new Error(`Unknown expression type: ${this.expression.constructor.name}`)
}
Expand Down
4 changes: 2 additions & 2 deletions javascript/src/GherkinQueryStream.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Transform, TransformCallback } from 'stream'
import { Query } from '@cucumber/gherkin-utils'
import { messages } from '@cucumber/messages'
import * as messages from '@cucumber/messages'

export default class GherkinQueryStream extends Transform {
constructor(private readonly gherkinQuery: Query) {
super({ readableObjectMode: true, writableObjectMode: true })
}

_transform(envelope: messages.IEnvelope, encoding: string, callback: TransformCallback): void {
_transform(envelope: messages.Envelope, encoding: string, callback: TransformCallback): void {
this.gherkinQuery.update(envelope)
callback(null, envelope)
}
Expand Down
29 changes: 17 additions & 12 deletions javascript/src/Hook.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
import parseTagExpression from '@cucumber/tag-expressions'
import { messages } from '@cucumber/messages'
import * as messages from '@cucumber/messages'
import SupportCodeExecutor from './SupportCodeExecutor'
import { IHook, AnyBody, ISupportCodeExecutor } from './types'

export default class Hook implements IHook {
constructor(
public readonly id: string,
private readonly tagExpression: string | null,
private readonly sourceReference: messages.ISourceReference,
private readonly sourceReference: messages.SourceReference,
private readonly body: AnyBody
) {}

public match(pickle: messages.IPickle): ISupportCodeExecutor | null {
public match(pickle: messages.Pickle): ISupportCodeExecutor | null {
const matches = this.tagExpression === null || this.matchesPickle(pickle)

return matches ? new SupportCodeExecutor(this.id, this.body, [], null, null) : null
}

public toMessage(): messages.IEnvelope {
return new messages.Envelope({
hook: new messages.Hook({
id: this.id,
tagExpression: this.tagExpression,
sourceReference: this.sourceReference,
}),
})
public toMessage(): messages.Envelope {
const hook: messages.Hook = {
id: this.id,
sourceReference: this.sourceReference,
}

if (this.tagExpression) {
hook.tagExpression = this.tagExpression
}

return {
hook,
}
}

private matchesPickle(pickle: messages.IPickle): boolean {
private matchesPickle(pickle: messages.Pickle): boolean {
const expression = parseTagExpression(this.tagExpression)
const tagNames = pickle.tags.map((tag) => tag.name)
return expression.evaluate(tagNames)
Expand Down
10 changes: 4 additions & 6 deletions javascript/src/HookTestStep.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { messages } from '@cucumber/messages'
import * as messages from '@cucumber/messages'
import TestStep from './TestStep'

export default class HookTestStep extends TestStep {
public toMessage(): messages.TestCase.ITestStep {
const testStep = new messages.TestCase.TestStep({
public toMessage(): messages.TestStep {
return {
id: this.id,
hookId: this.sourceId,
})
testStep.pickleStepId = undefined
return testStep
}
}
}
4 changes: 2 additions & 2 deletions javascript/src/IParameterTypeDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export default interface IParameterTypeDefinition {
name: string
regexp: RegExp | ReadonlyArray<RegExp> | string | ReadonlyArray<string>
regexp: RegExp | readonly RegExp[] | string | readonly string[]
type?: any
transformer?: (...args: ReadonlyArray<string>) => any
transformer?: (...args: readonly string[]) => any
preferForRegexpMatch?: boolean
useForSnippets?: boolean
}
17 changes: 7 additions & 10 deletions javascript/src/PickleTestStep.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import TestStep from './TestStep'
import { messages } from '@cucumber/messages'
import * as messages from '@cucumber/messages'

export default class PickleTestStep extends TestStep {
public toMessage(): messages.TestCase.ITestStep {
return new messages.TestCase.TestStep({
public toMessage(): messages.TestStep {
return {
id: this.id,
pickleStepId: this.sourceId,
stepDefinitionIds: this.supportCodeExecutors.map(
(supportCodeExecutor) => supportCodeExecutor.stepDefinitionId
),
stepMatchArgumentsLists: this.supportCodeExecutors.map(
(e) =>
new messages.TestCase.TestStep.StepMatchArgumentsList({
stepMatchArguments: e.argsToMessages().slice(),
})
),
})
stepMatchArgumentsLists: this.supportCodeExecutors.map((e) => ({
stepMatchArguments: e.argsToMessages().slice(),
})),
}
}
}
50 changes: 23 additions & 27 deletions javascript/src/SupportCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
ParameterType,
ParameterTypeRegistry,
} from '@cucumber/cucumber-expressions'
import { IdGenerator, messages } from '@cucumber/messages'
import * as messages from '@cucumber/messages'
import { IHook, AnyBody, IStepDefinition } from './types'
import ExpressionStepDefinition from './ExpressionStepDefinition'
import Hook from './Hook'
Expand All @@ -23,17 +23,17 @@ function defaultTransformer(...args: string[]) {
*/
export default class SupportCode {
public readonly parameterTypes: Array<ParameterType<any>> = []
public readonly parameterTypeMessages: Array<messages.IEnvelope> = []
public readonly parameterTypeMessages: Array<messages.Envelope> = []
public readonly stepDefinitions: IStepDefinition[] = []
public readonly beforeHooks: IHook[] = []
public readonly afterHooks: IHook[] = []

private readonly parameterTypeRegistry = new ParameterTypeRegistry()
private readonly expressionFactory = new ExpressionFactory(this.parameterTypeRegistry)
public readonly undefinedParameterTypeMessages: messages.IEnvelope[] = []
public readonly undefinedParameterTypeMessages: messages.Envelope[] = []

constructor(
public readonly newId: IdGenerator.NewId = IdGenerator.uuid(),
public readonly newId: messages.IdGenerator.NewId = messages.IdGenerator.uuid(),
public readonly clock: IClock = new DateClock(),
public readonly stopwatch: IStopwatch = new PerfHooksStopwatch(),
public readonly makeErrorMessage: MakeErrorMessage = withFullStackTrace()
Expand All @@ -50,21 +50,19 @@ export default class SupportCode {
)
this.parameterTypeRegistry.defineParameterType(parameterType)
this.parameterTypes.push(parameterType)
this.parameterTypeMessages.push(
new messages.Envelope({
parameterType: new messages.ParameterType({
id: this.newId(),
name: parameterType.name,
regularExpressions: parameterType.regexpStrings.slice(),
preferForRegularExpressionMatch: parameterType.preferForRegexpMatch,
useForSnippets: parameterType.useForSnippets,
}),
})
)
this.parameterTypeMessages.push({
parameterType: {
id: this.newId(),
name: parameterType.name,
regularExpressions: parameterType.regexpStrings.slice(),
preferForRegularExpressionMatch: parameterType.preferForRegexpMatch,
useForSnippets: parameterType.useForSnippets,
},
})
}

public defineStepDefinition(
sourceReference: messages.ISourceReference,
sourceReference: messages.SourceReference,
expression: string | RegExp,
body: AnyBody
): void {
Expand All @@ -74,14 +72,12 @@ export default class SupportCode {
this.registerStepDefinition(stepDefinition)
} catch (e) {
if (e.undefinedParameterTypeName) {
this.undefinedParameterTypeMessages.push(
new messages.Envelope({
undefinedParameterType: new messages.UndefinedParameterType({
expression: expression.toString(),
name: e.undefinedParameterTypeName,
}),
})
)
this.undefinedParameterTypeMessages.push({
undefinedParameterType: {
expression: expression.toString(),
name: e.undefinedParameterTypeName,
},
})
} else {
throw e
}
Expand All @@ -93,7 +89,7 @@ export default class SupportCode {
}

public defineBeforeHook(
sourceReference: messages.ISourceReference,
sourceReference: messages.SourceReference,
tagExpressionOrBody: string | AnyBody,
body?: AnyBody
) {
Expand All @@ -105,7 +101,7 @@ export default class SupportCode {
}

public defineAfterHook(
sourceReference: messages.ISourceReference,
sourceReference: messages.SourceReference,
tagExpressionOrBody: string | AnyBody,
body?: AnyBody
) {
Expand All @@ -117,7 +113,7 @@ export default class SupportCode {
}

private makeHook(
sourceReference: messages.ISourceReference,
sourceReference: messages.SourceReference,
tagExpressionOrBody: string | AnyBody,
body?: AnyBody
) {
Expand Down
22 changes: 10 additions & 12 deletions javascript/src/SupportCodeExecutor.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Argument, Group } from '@cucumber/cucumber-expressions'
import { messages } from '@cucumber/messages'
import * as messages from '@cucumber/messages'
import { AnyBody, ISupportCodeExecutor, IWorld } from './types'
import DataTable from './DataTable'

export default class SupportCodeExecutor implements ISupportCodeExecutor {
constructor(
public readonly stepDefinitionId: string,
private readonly body: AnyBody,
private readonly args: ReadonlyArray<Argument<any>>,
private readonly docString: messages.PickleStepArgument.IPickleDocString,
private readonly dataTable: messages.PickleStepArgument.IPickleTable
private readonly args: readonly Argument<any>[],
private readonly docString: messages.PickleDocString,
private readonly dataTable: messages.PickleTable
) {}

public execute(thisObj: IWorld): any {
Expand All @@ -24,22 +24,20 @@ export default class SupportCodeExecutor implements ISupportCodeExecutor {
return this.body.apply(thisObj, argArray)
}

public argsToMessages(): messages.TestCase.TestStep.StepMatchArgumentsList.IStepMatchArgument[] {
public argsToMessages(): messages.StepMatchArgument[] {
return this.args.map((arg) => {
return new messages.TestCase.TestStep.StepMatchArgumentsList.StepMatchArgument({
return {
group: toMessageGroup(arg.group),
parameterTypeName: arg.parameterType.name,
})
}
})
}
}

function toMessageGroup(
group: Group
): messages.TestCase.TestStep.StepMatchArgumentsList.StepMatchArgument.IGroup {
return new messages.TestCase.TestStep.StepMatchArgumentsList.StepMatchArgument.Group({
function toMessageGroup(group: Group): messages.Group {
return {
value: group.value,
start: group.start,
children: group.children.map((g) => toMessageGroup(g)),
})
}
}
Loading

0 comments on commit 065d679

Please sign in to comment.