Skip to content

Commit 889a1f2

Browse files
committed
add validations & tests for non-repetition of list attributes
1 parent d1bf996 commit 889a1f2

17 files changed

+770
-48
lines changed

src/language/context-mapper-dsl.langium

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,9 @@ Aggregate:
177177
"Aggregate" name=ID (OPEN
178178
(
179179
(('responsibilities' ('=')? responsibilities+=STRING) ("," responsibilities+=STRING)*) |
180-
(
181-
(('useCases' ('=')? useCases+=[UseCase]) ("," useCases+=[UseCase])*) |
182-
(('userStories' ('=')? userStories+=[UserStory]) ("," userStories+=[UserStory])*) |
183-
((('features' | 'userRequirements') ('=')? userRequirements+=[UserRequirement]) ("," userRequirements+=[UserRequirement])*)
184-
) |
180+
(('useCases' ('=')? useCases+=[UseCase]) ("," useCases+=[UseCase])*) |
181+
(('userStories' ('=')? userStories+=[UserStory]) ("," userStories+=[UserStory])*) |
182+
((('features' | 'userRequirements') ('=')? userRequirements+=[UserRequirement]) ("," userRequirements+=[UserRequirement])*) |
185183
('owner' ('=')? owner+=[BoundedContext]) |
186184
('knowledgeLevel' ('=')? knowledgeLevel+=KnowledgeLevel) |
187185
(('likelihoodForChange' | 'structuralVolatility') ('=')? likelihoodForChange+=Volatility) |

src/language/validation/ContextMapperValidationProviderRegistry.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
UpstreamDownstreamRelationship,
1515
UseCase,
1616
Value,
17-
ValueElicitation
17+
ValueElicitation, ValueEpic
1818
} from '../generated/ast.js'
1919
import { BoundedContextValidationProvider } from './impl/BoundedContextValidationProvider.js'
2020
import { SubDomainValidationProvider } from './impl/SubDomainValidationProvider.js'
@@ -26,6 +26,7 @@ import { UseCaseValidationProvider } from './impl/UseCaseValidationProvider.js'
2626
import { SculptorModuleValidationProvider } from './impl/SculptorModuleValidationProvider.js'
2727
import { StakeholderValidationProvider } from './impl/StakeholderValidationProvider.js'
2828
import { ValueElicitationValidationProvider } from './impl/ValueElicitationValidationProvider.js'
29+
import { ValueEpicValidationProvider } from './impl/ValueEpicValidationProvider.js'
2930

3031
export class ContextMapperValidationProviderRegistry {
3132
private readonly _providers = new Map<string, ContextMapperValidationProvider<AstNode>>([
@@ -39,7 +40,8 @@ export class ContextMapperValidationProviderRegistry {
3940
[UseCase, new UseCaseValidationProvider()],
4041
[SculptorModule, new SculptorModuleValidationProvider()],
4142
[Stakeholder, new StakeholderValidationProvider()],
42-
[ValueElicitation, new ValueElicitationValidationProvider()]
43+
[ValueElicitation, new ValueElicitationValidationProvider()],
44+
[ValueEpic, new ValueEpicValidationProvider()]
4345
])
4446

4547
get (node: AstNode): ContextMapperValidationProvider<AstNode> | undefined {
Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,57 @@
11
import { AstNode, ValidationAcceptor } from 'langium'
22

3-
export function enforceZeroOrOneCardinality (node: AstNode, property: string, acceptor: ValidationAcceptor, propertyName: string = property) {
3+
export function enforceZeroOrOneCardinality (node: AstNode, property: string, acceptor: ValidationAcceptor, keywords: string[] = [property]) {
44
const nodeProperty = node[property as keyof AstNode]
5-
if (!Array.isArray(nodeProperty)) {
6-
acceptor('warning', `There was a problem validating the element ${propertyName}.`, {
7-
node,
8-
property
5+
if (nodeProperty != null && !Array.isArray(nodeProperty)) {
6+
keywords.forEach(keyword => {
7+
acceptor('warning', `There was a problem validating the attribute ${keyword}.`, {
8+
node,
9+
keyword
10+
})
911
})
1012
return
1113
}
1214
if (nodeProperty != null && nodeProperty.length > 1) {
13-
acceptor('error', `There must be zero or one ${propertyName} attribute`, {
14-
node,
15-
property
15+
keywords.forEach(keyword => {
16+
acceptor('error', `There must be zero or one ${keyword} attribute`, {
17+
node,
18+
keyword
19+
})
20+
})
21+
}
22+
}
23+
24+
export function enforceZeroOrOneCardinalityOfListAttribute (node: AstNode, property: string, acceptor: ValidationAcceptor, keywords: string[] = [property]) {
25+
const nodeProperty = node[property as keyof AstNode]
26+
if (!Array.isArray(nodeProperty)) {
27+
keywords.forEach(keyword => {
28+
acceptor('warning', `There was a problem validating the attribute "${keywords.join(' | ')}".`, {
29+
node,
30+
keyword
31+
})
32+
})
33+
return
34+
}
35+
36+
if (nodeProperty == null || nodeProperty.length < 2 || node.$cstNode == null) {
37+
return
38+
}
39+
40+
let matchCount = 0
41+
keywords.forEach(keyword => {
42+
const regex = new RegExp(`(?<![\\w'"])\\b${keyword}\\b(?![\\w'"])`, 'g')
43+
const match = node.$cstNode!.text.match(regex)
44+
if (match) {
45+
matchCount += match.length
46+
}
47+
})
48+
49+
if (matchCount > 1) {
50+
keywords.forEach(keyword => {
51+
acceptor('error', `There must be zero or one "${keywords.join(' | ')}" attribute`, {
52+
node,
53+
keyword
54+
})
1655
})
1756
}
1857
}
Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,38 @@
11
import { ContextMapperValidationProvider } from '../ContextMapperValidationProvider.js'
22
import { Aggregate } from '../../generated/ast.js'
3-
import { ValidationAcceptor } from 'langium'
4-
import { enforceZeroOrOneCardinality } from '../ValidationHelper.js'
3+
import { Properties, ValidationAcceptor } from 'langium'
4+
import { enforceZeroOrOneCardinality, enforceZeroOrOneCardinalityOfListAttribute } from '../ValidationHelper.js'
55

66
export class AggregateValidationProvider implements ContextMapperValidationProvider<Aggregate> {
77
validate (node: Aggregate, acceptor: ValidationAcceptor): void {
8-
// TODO: regex enforce responsibilities
9-
// TODO: regex enforce useCases
10-
// TODO: regex enforce userStories
11-
// TODO: regex enforce userRequirements & features
12-
8+
enforceZeroOrOneCardinalityOfListAttribute(node, 'responsibilities', acceptor)
9+
enforceZeroOrOneCardinalityOfListAttribute(node, 'useCases', acceptor)
10+
enforceZeroOrOneCardinalityOfListAttribute(node, 'userStories', acceptor)
11+
enforceZeroOrOneCardinalityOfListAttribute(node, 'userRequirements', acceptor, ['userRequirements', 'features'])
1312
enforceZeroOrOneCardinality(node, 'owner', acceptor)
1413
enforceZeroOrOneCardinality(node, 'knowledgeLevel', acceptor)
15-
enforceZeroOrOneCardinality(node, 'likelihoodForChange', acceptor)
14+
enforceZeroOrOneCardinality(node, 'likelihoodForChange', acceptor, ['likelihoodForChange', 'structuralVolatility'])
1615
enforceZeroOrOneCardinality(node, 'contentVolatility', acceptor)
1716
enforceZeroOrOneCardinality(node, 'availabilityCriticality', acceptor)
1817
enforceZeroOrOneCardinality(node, 'consistencyCriticality', acceptor)
1918
enforceZeroOrOneCardinality(node, 'storageSimilarity', acceptor)
2019
enforceZeroOrOneCardinality(node, 'securityCriticality', acceptor)
2120
enforceZeroOrOneCardinality(node, 'securityZone', acceptor)
2221
enforceZeroOrOneCardinality(node, 'securityAccessGroup', acceptor)
22+
23+
// make sure only one of the userRequirements keywords is used
24+
const userRequirementProperties = ['userRequirements', 'useCases', 'userStories'] as Array<keyof Aggregate>
25+
const setUserRequirements = userRequirementProperties.filter(p => {
26+
const value = node[p] as Array<any>
27+
return value.length > 0
28+
})
29+
if (setUserRequirements.length > 1) {
30+
setUserRequirements.forEach(property => {
31+
acceptor('error', 'One ony of the keywords "userRequirements", "features", "useCases" and "userStories" may be used', {
32+
node,
33+
property: property as Properties<Aggregate>
34+
})
35+
})
36+
}
2337
}
2438
}

src/language/validation/impl/BoundedContextValidationProvider.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { ContextMapperValidationProvider } from '../ContextMapperValidationProvider.js'
22
import { BoundedContext } from '../../generated/ast.js'
33
import { ValidationAcceptor } from 'langium'
4-
import { enforceZeroOrOneCardinality } from '../ValidationHelper.js'
4+
import { enforceZeroOrOneCardinality, enforceZeroOrOneCardinalityOfListAttribute } from '../ValidationHelper.js'
55

66
export class BoundedContextValidationProvider implements ContextMapperValidationProvider<BoundedContext> {
77
validate (node: BoundedContext, acceptor: ValidationAcceptor): void {
8-
// TODO: regex enforce implementedDomainParts
9-
// TODO: regex enforce realizedBoundedContexts
10-
enforceZeroOrOneCardinality(node, 'refinedBoundedContext', acceptor, 'refines')
8+
enforceZeroOrOneCardinalityOfListAttribute(node, 'implementedDomainParts', acceptor, ['implements'])
9+
enforceZeroOrOneCardinalityOfListAttribute(node, 'realizedBoundedContexts', acceptor, ['realizes'])
10+
enforceZeroOrOneCardinality(node, 'refinedBoundedContext', acceptor, ['refines'])
1111

1212
enforceZeroOrOneCardinality(node, 'domainVisionStatement', acceptor)
1313
enforceZeroOrOneCardinality(node, 'type', acceptor)
14-
// TODO: regex enforce responsibilities
14+
enforceZeroOrOneCardinalityOfListAttribute(node, 'responsibilities', acceptor)
1515
enforceZeroOrOneCardinality(node, 'implementationTechnology', acceptor)
1616
enforceZeroOrOneCardinality(node, 'knowledgeLevel', acceptor)
1717
enforceZeroOrOneCardinality(node, 'businessModel', acceptor)
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { ContextMapperValidationProvider } from '../ContextMapperValidationProvider.js'
22
import { UpstreamDownstreamRelationship } from '../../generated/ast.js'
33
import { ValidationAcceptor } from 'langium'
4-
import { enforceZeroOrOneCardinality } from '../ValidationHelper.js'
4+
import { enforceZeroOrOneCardinality, enforceZeroOrOneCardinalityOfListAttribute } from '../ValidationHelper.js'
55

66
export class UpstreamDownstreamRelationshipValidationProvider implements ContextMapperValidationProvider<UpstreamDownstreamRelationship> {
77
validate (node: UpstreamDownstreamRelationship, acceptor: ValidationAcceptor): void {
88
enforceZeroOrOneCardinality(node, 'implementationTechnology', acceptor)
9-
// TODO: regex enforce exposedAggregates
10-
enforceZeroOrOneCardinality(node, 'downstreamRights', acceptor)
9+
enforceZeroOrOneCardinalityOfListAttribute(node, 'upstreamExposedAggregates', acceptor, ['exposedAggregates'])
10+
enforceZeroOrOneCardinality(node, 'downstreamGovernanceRights', acceptor, ['downstreamRights'])
1111
}
1212
}

src/language/validation/impl/UseCaseValidationProvider.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { ContextMapperValidationProvider } from '../ContextMapperValidationProvider.js'
22
import { isUseCase, UserRequirement } from '../../generated/ast.js'
33
import { ValidationAcceptor } from 'langium'
4-
import { enforceZeroOrOneCardinality } from '../ValidationHelper.js'
4+
import { enforceZeroOrOneCardinality, enforceZeroOrOneCardinalityOfListAttribute } from '../ValidationHelper.js'
55

66
export class UseCaseValidationProvider implements ContextMapperValidationProvider<UserRequirement> {
77
validate (node: UserRequirement, acceptor: ValidationAcceptor): void {
88
if (isUseCase(node)) {
9-
enforceZeroOrOneCardinality(node, 'role', acceptor, 'actor')
10-
// TODO: regex enforce secondaryActors
11-
// TODO: regex enforce features
9+
enforceZeroOrOneCardinality(node, 'role', acceptor, ['actor'])
10+
enforceZeroOrOneCardinalityOfListAttribute(node, 'secondaryActors', acceptor)
11+
enforceZeroOrOneCardinalityOfListAttribute(node, 'features', acceptor, ['interactions'])
1212
enforceZeroOrOneCardinality(node, 'benefit', acceptor)
1313
enforceZeroOrOneCardinality(node, 'scope', acceptor)
1414
enforceZeroOrOneCardinality(node, 'level', acceptor)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { ContextMapperValidationProvider } from '../ContextMapperValidationProvider.js'
22
import { ValueElicitation } from '../../generated/ast.js'
33
import { ValidationAcceptor } from 'langium'
4-
import { enforceZeroOrOneCardinality } from '../ValidationHelper.js'
4+
import { enforceZeroOrOneCardinality, enforceZeroOrOneCardinalityOfListAttribute } from '../ValidationHelper.js'
55

66
export class ValueElicitationValidationProvider implements ContextMapperValidationProvider<ValueElicitation> {
77
validate (node: ValueElicitation, acceptor: ValidationAcceptor): void {
88
enforceZeroOrOneCardinality(node, 'priority', acceptor)
99
enforceZeroOrOneCardinality(node, 'impact', acceptor)
10-
// TODO: regex enforce consequences
10+
enforceZeroOrOneCardinalityOfListAttribute(node, 'consequences', acceptor)
1111
}
1212
}

src/language/validation/impl/ValueEpicValidationProvider.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@ import { ValidationAcceptor } from 'langium'
44

55
export class ValueEpicValidationProvider implements ContextMapperValidationProvider<ValueEpic> {
66
validate (node: ValueEpic, acceptor: ValidationAcceptor): void {
7-
// TODO: regex enforce realizedValues
8-
// TODO: regex enforce reducedValues
7+
if (node.reducedValues.length === 0) {
8+
acceptor('error', 'At least one reduced value is required', {
9+
node,
10+
property: 'reducedValues'
11+
})
12+
}
13+
14+
if (node.realizedValues.length === 0) {
15+
acceptor('error', 'At least one realized value is required', {
16+
node,
17+
property: 'realizedValues'
18+
})
19+
}
920
}
1021
}

src/language/validation/impl/ValueValidationProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ import { enforceZeroOrOneCardinality } from '../ValidationHelper.js'
55

66
export class ValueValidationProvider implements ContextMapperValidationProvider<Value> {
77
validate (node: Value, acceptor: ValidationAcceptor): void {
8-
enforceZeroOrOneCardinality(node, 'coreValue', acceptor, 'isCore')
8+
enforceZeroOrOneCardinality(node, 'coreValue', acceptor, ['isCore'])
99
}
1010
}

0 commit comments

Comments
 (0)