Skip to content

Commit

Permalink
add polymorphic subtype rule
Browse files Browse the repository at this point in the history
  • Loading branch information
jenschude committed Apr 19, 2024
1 parent f1c926a commit d1d41f0
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.commercetools.rmf.validators

import io.vrap.rmf.raml.model.modules.util.ModulesSwitch
import io.vrap.rmf.raml.model.resources.util.ResourcesSwitch
import io.vrap.rmf.raml.model.types.AnnotationsFacet
import io.vrap.rmf.raml.model.types.ArrayInstance
import io.vrap.rmf.raml.validation.AbstractRamlValidator
import io.vrap.rmf.raml.validation.RamlValidator
import org.eclipse.emf.common.util.Diagnostic
Expand All @@ -19,7 +21,13 @@ class ModulesValidator(private val validators: List<ModulesSwitch<List<Diagnosti
context: MutableMap<Any, Any>?
): Boolean {
val validationResults = validators.stream()
.flatMap { validator: ModulesSwitch<List<Diagnostic>> -> validator.doSwitch(eObject).stream() }
.flatMap { validator: ModulesSwitch<List<Diagnostic>> ->
return@flatMap if (eObject is AnnotationsFacet && eObject.getAnnotation("ignoreValidators") != null && (eObject.getAnnotation("ignoreValidators").value as ArrayInstance).value.any { it.value == validator.javaClass.simpleName } ) {
emptyList<Diagnostic>().stream()
} else {
validator.doSwitch(eObject).stream()
}
}
.collect(Collectors.toList())
validationResults.forEach(Consumer { diagnostic: Diagnostic? -> diagnostics.add(diagnostic) })
return validationResults.isEmpty()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.commercetools.rmf.validators

import io.vrap.rmf.raml.model.modules.Library
import io.vrap.rmf.raml.model.types.AnyType
import io.vrap.rmf.raml.model.types.BuiltinType
import io.vrap.rmf.raml.model.types.ObjectType
import io.vrap.rmf.raml.model.types.TypeTemplate
import org.eclipse.emf.common.util.Diagnostic
import java.util.*

//@ValidatorSet
class PolymorphicSubtypesRule(severity: RuleSeverity, options: List<RuleOption>? = null) : TypesRule(severity, options) {

private val exclude: List<String> =
(options?.filter { ruleOption -> ruleOption.type.lowercase(Locale.getDefault()) == RuleOptionType.EXCLUDE.toString() }?.map { ruleOption -> ruleOption.value }?.plus("") ?: defaultExcludes)

override fun caseAnyType(type: AnyType): List<Diagnostic> {
val validationResults: MutableList<Diagnostic> = ArrayList()

if (exclude.contains(type.name).not() && type is ObjectType && type.subTypes.filterNot { it.isInlineType }.any() && type.discriminator == null) {
validationResults.add(create(type, "Type \"{0}\" has subtypes but no discriminator is set", type.name))
}
return validationResults
}

companion object : ValidatorFactory<PolymorphicSubtypesRule> {
private val defaultExcludes by lazy { listOf("") }

@JvmStatic
override fun create(options: List<RuleOption>?): PolymorphicSubtypesRule {
return PolymorphicSubtypesRule(RuleSeverity.ERROR, options)
}

@JvmStatic
override fun create(severity: RuleSeverity, options: List<RuleOption>?): PolymorphicSubtypesRule {
return PolymorphicSubtypesRule(severity, options)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.commercetools.rmf.validators

import io.vrap.rmf.raml.model.resources.util.ResourcesSwitch
import io.vrap.rmf.raml.model.types.AnnotationsFacet
import io.vrap.rmf.raml.model.types.ArrayInstance
import io.vrap.rmf.raml.validation.AbstractRamlValidator
import io.vrap.rmf.raml.validation.RamlValidator
import io.vrap.rmf.raml.validation.ResolvedRamlValidator
Expand All @@ -20,7 +22,13 @@ class ResolvedResourcesValidator(private val validators: List<ResourcesSwitch<Li
context: Map<Any, Any>
): Boolean {
val validationResults = validators.stream()
.flatMap { validator: ResourcesSwitch<List<Diagnostic>> -> validator.doSwitch(eObject).stream() }
.flatMap { validator: ResourcesSwitch<List<Diagnostic>> ->
return@flatMap if (eObject is AnnotationsFacet && eObject.getAnnotation("ignoreValidators") != null && (eObject.getAnnotation("ignoreValidators").value as ArrayInstance).value.any { it.value == validator.javaClass.simpleName } ) {
emptyList<Diagnostic>().stream()
} else {
validator.doSwitch(eObject).stream()
}
}
.collect(Collectors.toList())
validationResults.forEach(Consumer { diagnostic: Diagnostic? -> diagnostics.add(diagnostic) })
return validationResults.isEmpty()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.commercetools.rmf.validators

import io.vrap.rmf.raml.model.resources.util.ResourcesSwitch
import io.vrap.rmf.raml.model.types.AnnotationsFacet
import io.vrap.rmf.raml.model.types.ArrayInstance
import io.vrap.rmf.raml.validation.AbstractRamlValidator
import io.vrap.rmf.raml.validation.RamlValidator
import org.eclipse.emf.common.util.Diagnostic
Expand All @@ -19,7 +21,13 @@ class ResourcesValidator(private val validators: List<ResourcesSwitch<List<Diagn
context: Map<Any, Any>
): Boolean {
val validationResults = validators.stream()
.flatMap { validator: ResourcesSwitch<List<Diagnostic>> -> validator.doSwitch(eObject).stream() }
.flatMap { validator: ResourcesSwitch<List<Diagnostic>> ->
return@flatMap if (eObject is AnnotationsFacet && eObject.getAnnotation("ignoreValidators") != null && (eObject.getAnnotation("ignoreValidators").value as ArrayInstance).value.any { it.value == validator.javaClass.simpleName } ) {
emptyList<Diagnostic>().stream()
} else {
validator.doSwitch(eObject).stream()
}
}
.collect(Collectors.toList())
validationResults.forEach(Consumer { diagnostic: Diagnostic? -> diagnostics.add(diagnostic) })
return validationResults.isEmpty()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.commercetools.rmf.validators

import io.vrap.rmf.raml.model.resources.util.ResourcesSwitch
import io.vrap.rmf.raml.model.types.AnnotationsFacet
import io.vrap.rmf.raml.model.types.ArrayInstance
import io.vrap.rmf.raml.model.types.util.TypesSwitch
import io.vrap.rmf.raml.validation.AbstractRamlValidator
import io.vrap.rmf.raml.validation.RamlValidator
Expand All @@ -20,7 +21,13 @@ class TypesValidator(private val validators: List<TypesSwitch<List<Diagnostic>>>
context: Map<Any, Any>
): Boolean {
val validationResults = validators.stream()
.flatMap { validator: TypesSwitch<List<Diagnostic>> -> validator.doSwitch(eObject).stream() }
.flatMap { validator: TypesSwitch<List<Diagnostic>> ->
return@flatMap if (eObject is AnnotationsFacet && eObject.getAnnotation("ignoreValidators") != null && (eObject.getAnnotation("ignoreValidators").value as ArrayInstance).value.any { it.value == validator.javaClass.simpleName } ) {
emptyList<Diagnostic>().stream()
} else {
validator.doSwitch(eObject).stream()
}
}
.collect(Collectors.toList())
validationResults.forEach(Consumer { diagnostic: Diagnostic? -> diagnostics.add(diagnostic) })
return validationResults.isEmpty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,4 +397,14 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures {
result.validationResults[0].message == "Property \"/invalid/\" must define object type for placeholder annotation"
}

def "subtypes should be discriminated"() {
when:
def validators = Arrays.asList(new TypesValidator(Arrays.asList(PolymorphicSubtypesRule.create(emptyList()))))
def uri = uriFromClasspath("/polymorphic-subtype-rule.raml")
def result = new RamlModelBuilder(validators).buildApi(uri)
then:
result.validationResults.size == 1
result.validationResults[0].message == "Type \"InvalidBaz\" has subtypes but no discriminator is set"
}

}
38 changes: 38 additions & 0 deletions ctp-validators/src/test/resources/polymorphic-subtype-rule.raml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#%RAML 1.0
title: discriminator subtype rule

types:
Foo:
type: object
discriminator: type
properties:
type: string
SubFoo:
discriminatorValue: sub
type: Foo
Bar:
type: object
description: InvalidBar
properties:
name: string
FooBar:
description: FooBar
type: object
properties:
bar: Bar
FooBar2:
type: object
properties:
bar:
description: Bar
type: Bar
InvalidBaz:
type: object
description: InvalidBar
properties:
name: string
SubBaz:
description: SubBaz
type: InvalidBaz
SubBaz2:
type: InvalidBaz

0 comments on commit d1d41f0

Please sign in to comment.