Skip to content

Commit

Permalink
Fix some anomaly checking with patternProperties
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelmior committed Mar 6, 2024
1 parent e0bb6d0 commit 9595637
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Fixed
- Fix some anomaly checking with `patternProperties`

## [0.20.1]
### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,41 @@ final case class ObjectSchema(
}
}
}

override def collectAnomalies[S <: JValue](
value: S,
path: String = "$"
)(implicit p: JsonoidParams, tag: ClassTag[S]): Seq[Anomaly] = {
if (
isValidType(value) &&
properties.has[PatternTypesProperty] &&
properties.has[ObjectTypesProperty]
) {
// Check if additional properties are allowed
val allowAdditional = properties
.getOrNone[AdditionalPropertiesProperty]
.map(_.allowAdditional)
.getOrElse(p.additionalProperties)

// Get anomalies from patternProperties while allowing
// the possibility of explicitly defined keys elsewhere
val patternAnomalies = properties
.get[PatternTypesProperty]
.collectAnomaliesAllowMissing(
value,
path,
properties.get[ObjectTypesProperty].objectTypes.keySet,
allowAdditional
)

patternAnomalies ++ properties
.without(List(classOf[PatternTypesProperty]))
.flatMap(_.collectAnomalies(value, path)(p, tag))
.toSeq
} else {
super.collectAnomalies(value, path)(p, tag)
}
}
}

/** The types of all keys in an object schema.
Expand Down Expand Up @@ -556,6 +591,15 @@ final case class PatternTypesProperty(
p: JsonoidParams,
tag: ClassTag[S]
): Seq[Anomaly] = {
collectAnomaliesAllowMissing(value, path, Set(), p.additionalProperties)
}

def collectAnomaliesAllowMissing[S <: JValue](
value: S,
path: String,
missingOk: Set[String],
additionalProperties: Boolean
)(implicit p: JsonoidParams, tag: ClassTag[S]): Seq[Anomaly] = {
value match {
case JObject(fields) =>
fields.flatMap { case (key, value) =>
Expand All @@ -570,14 +614,15 @@ final case class PatternTypesProperty(
// or report that there is no pattern that matches
patternSchema match {
case Some(schema) => schema.collectAnomalies(value, f"$path.$key")
case None =>
case None if !additionalProperties && !missingOk.contains(key) =>
Seq(
Anomaly(
f"$path.$key",
"found field not matching pattern",
AnomalyLevel.Fatal
)
)
case None => Seq.empty
}
}
case _ => Seq.empty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ class ObjectSchemaSpec extends UnitSpec with ScalaCheckPropertyChecks {
nestedSchema.entropy shouldBe Some(25)
}

it should "not find an anomaly if a pattern does not match, but is defined in properties" in {
val props = SchemaProperties.empty[Map[String, JsonSchema[_]]]
props.add(PatternTypesProperty(Map("^foo.*".r -> NumberSchema(3))))
props.add(ObjectTypesProperty(Map("bar" -> NumberSchema(3))))

ObjectSchema(props).collectAnomalies(
JObject(List(("bar", JInt(3))))
) shouldBe empty
}

behavior of "ObjectTypesProperty"

it should "calculate the intersection of properties" in {
Expand Down Expand Up @@ -105,6 +115,13 @@ class ObjectSchemaSpec extends UnitSpec with ScalaCheckPropertyChecks {
)
}

it should "not find an anomaly if a pattern does not match, but additionalProperties=true" in {
implicit val params =
JsonoidParams().withAdditionalProperties(true)
PatternTypesProperty(Map("^foo.*".r -> NumberSchema(3)))
.collectAnomalies(JObject(List(("baz", JInt(3))))) shouldBe empty
}

behavior of "RequiredProperty"

it should "track required properties" in {
Expand Down

0 comments on commit 9595637

Please sign in to comment.