Skip to content

Commit

Permalink
[WIP] Demo of maxJsonDepth when validating JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
istreeter committed Jul 31, 2024
1 parent 3929580 commit 112a051
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ import io.circe.{DecodingFailure, Json}
*/
final class IgluCirceClient[F[_]] private (
resolver: Resolver[F],
schemaEvaluationCache: SchemaEvaluationCache[F]
schemaEvaluationCache: SchemaEvaluationCache[F],
maxJsonDepth: Int
) {
def check(
instance: SelfDescribingData[Json]
Expand All @@ -50,7 +51,8 @@ final class IgluCirceClient[F[_]] private (
resolver.lookupSchemaResult(instance.schema, resolveSupersedingSchema = true)
)
validation =
CirceValidator.WithCaching.validate(schemaEvaluationCache)(instance.data, resolverResult)
CirceValidator.WithCaching
.validate(schemaEvaluationCache)(instance.data, resolverResult, maxJsonDepth)
_ <- EitherT(validation).leftMap(e =>
e.toClientError(resolverResult.value.supersededBy.map(_.asString))
)
Expand All @@ -61,21 +63,36 @@ final class IgluCirceClient[F[_]] private (

object IgluCirceClient {

@deprecated("Use `parseDefault(json, maxJsonDepth)`", "3.2.0")
def parseDefault[F[_]: Monad: CreateResolverCache: InitValidatorCache](
json: Json
): EitherT[F, DecodingFailure, IgluCirceClient[F]] =
parseDefault(json, Int.MaxValue)

def parseDefault[F[_]: Monad: CreateResolverCache: InitValidatorCache](
json: Json,
maxJsonDepth: Int
): EitherT[F, DecodingFailure, IgluCirceClient[F]] =
for {
config <- EitherT.fromEither[F](Resolver.parseConfig(json))
resolver <- Resolver.fromConfig[F](config)
client <- EitherT.liftF(fromResolver(resolver, config.cacheSize))
client <- EitherT.liftF(fromResolver(resolver, config.cacheSize, maxJsonDepth))
} yield client

@deprecated("Use `fromResolver(resolver, cacheSize, maxJsonDepth)`", "3.2.0")
def fromResolver[F[_]: Monad: InitValidatorCache](
resolver: Resolver[F],
cacheSize: Int
): F[IgluCirceClient[F]] =
fromResolver(resolver, cacheSize, Int.MaxValue)

def fromResolver[F[_]: Monad: InitValidatorCache](
resolver: Resolver[F],
cacheSize: Int,
maxJsonDepth: Int
): F[IgluCirceClient[F]] = {
schemaEvaluationCache[F](cacheSize).map { cache =>
new IgluCirceClient(resolver, cache)
new IgluCirceClient(resolver, cache, maxJsonDepth)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,25 +88,26 @@ object CirceValidator extends Validator[Json] {
V4SchemaInstance.getSchema(new ObjectMapper().readTree(MetaSchemas.JsonSchemaV4Text))

def validate(data: Json, schema: Json): Either[ValidatorError, Unit] = {
val jacksonJson = circeToJackson(schema)
val jacksonJson = circeToJackson(schema, Int.MaxValue)
evaluateSchema(jacksonJson)
.flatMap { schema =>
validateOnReadySchema(schema, data).leftMap(ValidatorError.InvalidData.apply)
validateOnReadySchema(schema, data, Int.MaxValue).leftMap(ValidatorError.InvalidData.apply)
}
}

def checkSchema(schema: Json): List[ValidatorError.SchemaIssue] = {
val jacksonJson = circeToJackson(schema)
val jacksonJson = circeToJackson(schema, Int.MaxValue)
validateSchemaAgainstV4(jacksonJson)
}

/** Validate instance against schema and return same instance */
private def validateOnReadySchema(
schema: JsonSchema,
instance: Json
instance: Json,
maxJsonDepth: Int
): EitherNel[ValidatorReport, Unit] = {
val messages = schema
.validate(circeToJackson(instance))
.validate(circeToJackson(instance, maxJsonDepth))
.asScala
.toList
.map(fromValidationMessage)
Expand Down Expand Up @@ -154,38 +155,46 @@ object CirceValidator extends Validator[Json] {

def validate[F[_]: Monad](
schemaEvaluationCache: SchemaEvaluationCache[F]
)(data: Json, schema: SchemaLookupResult): F[Either[ValidatorError, Unit]] = {
getFromCacheOrEvaluate(schemaEvaluationCache)(schema)
)(
data: Json,
schema: SchemaLookupResult,
maxJsonDepth: Int
): F[Either[ValidatorError, Unit]] = {
getFromCacheOrEvaluate(schemaEvaluationCache)(schema, maxJsonDepth)
.map {
_.flatMap { jsonschema =>
validateOnReadySchema(jsonschema, data)
validateOnReadySchema(jsonschema, data, maxJsonDepth)
.leftMap(ValidatorError.InvalidData.apply)
}
}
}

private def getFromCacheOrEvaluate[F[_]: Monad](
evaluationCache: SchemaEvaluationCache[F]
)(result: SchemaLookupResult): F[Either[ValidatorError.InvalidSchema, JsonSchema]] = {
)(
result: SchemaLookupResult,
maxJsonDepth: Int
): F[Either[ValidatorError.InvalidSchema, JsonSchema]] = {
result match {
case ResolverResult.Cached(key, SchemaItem(schema, _), timestamp) =>
evaluationCache.get((key, timestamp)).flatMap {
case Some(alreadyEvaluatedSchema) =>
alreadyEvaluatedSchema.pure[F]
case None =>
provideNewJsonSchema(schema)
provideNewJsonSchema(schema, maxJsonDepth)
.pure[F]
.flatTap(result => evaluationCache.put((key, timestamp), result))
}
case ResolverResult.NotCached(SchemaItem(schema, _)) =>
provideNewJsonSchema(schema).pure[F]
provideNewJsonSchema(schema, maxJsonDepth).pure[F]
}
}

private def provideNewJsonSchema(
schema: Json
schema: Json,
maxJsonDepth: Int
): Either[ValidatorError.InvalidSchema, JsonSchema] = {
val schemaAsNode = circeToJackson(schema)
val schemaAsNode = circeToJackson(schema, maxJsonDepth)
for {
_ <- validateSchema(schemaAsNode)
evaluated <- evaluateSchema(schemaAsNode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ package object snowplow {
* @param json instance of circe's Json
* @return converted JsonNode
*/
final def circeToJackson(json: Json): JsonNode =
final def circeToJackson(json: Json, maxDepth: Int): JsonNode =
json.fold(
NullNode.instance,
BooleanNode.valueOf(_),
Expand Down Expand Up @@ -71,12 +71,14 @@ package object snowplow {
}
},
s => TextNode.valueOf(s),
array => JsonNodeFactory.instance.arrayNode.addAll(array.map(circeToJackson).asJava),
array =>
JsonNodeFactory.instance.arrayNode
.addAll(array.map(circeToJackson(_, maxDepth - 1)).asJava),
obj =>
objectNodeSetAll(
JsonNodeFactory.instance.objectNode,
obj.toMap.map { case (k, v) =>
(k, circeToJackson(v))
(k, circeToJackson(v, maxDepth - 1))
}.asJava
)
)
Expand Down

0 comments on commit 112a051

Please sign in to comment.