Core grammar
The core grammar is the base, media type-agnostic language consisting of common hypermedia controls.
It can be used on its own using the .api extension.
Supported steps
Raw Xtext grammar
grammar app.hypermedia.testing.dsl.Core with org.eclipse.xtext.common.Terminals
generate core "http://testing.hypermedia.app/dsl/core"
CoreScenario:
    (entrypoint=EntrypointStep)?
    (defaultHeaders=ScenarioHeadersBlock)?
    (steps+=TopLevelStep)*;
TopLevelStep:
	ClassBlock |
	RelaxedLinkBlock;
	
EntrypointStep:
    'ENTRYPOINT' path=STRING
;
ResponseStep:
    StatusStatement |
    HeaderStatement |
    RepresentationStep |
    FollowStatement
;
RepresentationStep:
    PropertyBlock |
    PropertyStatement |
    LinkStep
;
LinkStep:
    LinkStatement |
    RelaxedLinkBlock |
    StrictLinkBlock
;
Identifier: value=STRING;
Modifier: WithModifier | ExpectModifier;
ExpectModifier: 'Expect';
WithModifier: 'With';
ScenarioHeadersBlock: 'HEADERS' '{'
    (headers+=RequestHeader)+
'}';
ClassBlock:
	WithModifier 'Class' name=Identifier '{'
        (constraints+=RepresentationConstraint)*
        (children+=RepresentationStep)*
    '}'
;
PropertyBlock:
    modifier=Modifier 'Property' name=Identifier '{'
        (constraints+=RepresentationConstraint)*
        (children+=RepresentationStep)*
    '}'
;
PropertyStatement:
    ExpectModifier 'Property' name=Identifier expectation=(
        IntValue |
        DecimalValue |
        BooleanValue |
        StringValue)?
;
DecimalValue:
    value=DECIMAL
;
BooleanValue:
    value=('true' | 'false')
;
IntValue:
    value=INT
;
StringValue:
    value=STRING
;
StatusStatement:
    ExpectModifier 'Status' status=INT
;
RelaxedLinkBlock:
    WithModifier 'Link' relation=Identifier '{'
        (constraints+=ResponseConstraint)*
        (children+=ResponseStep)*
    '}'
;
StrictLinkBlock:
    ExpectModifier 'Link' relation=Identifier '{'
        (constraints+=ResponseConstraint)*
        (children+=ResponseStep)*
    '}'
;
LinkStatement:
    ExpectModifier 'Link' relation=Identifier
;
HeaderStatement:
    ExpectModifier 'Header' fieldName=FieldName (exactValue=STRING | regex=MatchingRegex | variable=VARIABLE)?
;
MatchingRegex:
    'Matching' pattern=STRING
;
FollowStatement:
    'Follow' variable=VARIABLE
;
RequestBlock:
    {RequestBlock} '{'
        (headers+=RequestHeader)*
        (body=RequestBody)?
    '}'
;
ResponseBlock:
    {ResponseBlock} '{'
        (children+=ResponseStep)*
    '}'
;
RequestBody:
    contents=MULTILINE_BLOCK |
    reference=RequestFileBody
;
ResponseConstraint:
    RepresentationConstraint |
    StatusConstraint
;
RepresentationConstraint:
    PropertyConstraint
;
IntCondition:
    operator=(EqualityOperator | InequalityOperator)
    value=INT
;
StringCondition:
    operator=(EqualityOperator | InequalityOperator)
    value=STRING
;
DecimalCondition:
    operator=(EqualityOperator | InequalityOperator)
    value=DECIMAL
;
RegexCondition:
    operator=('Matches' | 'Matching')
    value=STRING
;
BooleanCondition:
    operator=EqualityOperator
    value=('true' | 'false')
;
CustomCondition:
    operator='=>'
    value=MULTILINE_BLOCK
;
StatusConstraint:
    'When' 'Status'
    negation='Not'?
    condition=(IntCondition | CustomCondition)
;
PropertyConstraint:
    'When' 'Property'
    name=Identifier?
    negation='Not'?
    condition=(
        IntCondition |
        StringCondition |
        DecimalCondition |
        RegexCondition |
        BooleanCondition |
        CustomCondition
    )
;
EqualityOperator:
    'Equal' | 'Equals'
;
RegexOperator:
    'Matches' | 'Matching'
;
InequalityOperator: 
    ('Less' | 'Greater') 'Than' ('Or' 'Equal')?
;
RequestFileBody:
    '<<<' path=STRING
;
RequestHeader:
    fieldName=FieldName value=STRING
;
FieldName: (ID | HYPHENATED_NAME);
terminal VARIABLE: '[' ID ']';
terminal HYPHENATED_NAME: ('a'..'z' | 'A'..'Z') ('-' | 'a'..'z' | 'A'..'Z')*;
terminal MULTILINE_BLOCK:
    '```' -> '```'
;
terminal DECIMAL: INT '.' INT;
