Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/JavaBasedProjectConventions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
fun Project.javaBasedProjectConventions() {
repositories {
mavenCentral()
mavenLocal()
}

dependencies {
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ protobuf-java = "4.30.1"
protobuf-js = "7.4.0"
protobufGradlePlugin = "0.9.5"
protovalidate = "0.13.0"
protovalidateJava = "0.12.0"
protovalidateJava = "0.0.0-SNAPSHOT"
slf4j = "2.0.17"

# build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,15 @@ internal class ProtoktEvaluatorBuilder(
internal class ProtoktEvaluator(
private val evaluator: Evaluator
) {
fun evaluate(message: Message, runtimeContext: RuntimeContext, failFast: Boolean) =
evaluator.evaluate(MessageValue(message.toDynamicMessage(runtimeContext)), failFast)
.let(::ProtoktRuleViolationBuilders)
fun evaluate(message: Message, runtimeContext: RuntimeContext, failFast: Boolean, lazyConvert: Boolean) =
evaluator.evaluate(
if (lazyConvert) {
ProtoktMessageValue(message, runtimeContext)
} else {
MessageValue(message.toDynamicMessage(runtimeContext))
},
failFast
).let(::ProtoktRuleViolationBuilders)
}

internal class ProtoktRuleViolationBuilders(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright (c) 2025 Toast, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package build.buf.protovalidate

import com.google.common.primitives.UnsignedLong
import com.google.protobuf.Descriptors.FieldDescriptor
import protokt.v1.Bytes
import protokt.v1.Enum
import protokt.v1.Message
import protokt.v1.google.protobuf.RuntimeContext
import protokt.v1.google.protobuf.getField
import protokt.v1.google.protobuf.hasField

internal class ProtoktMessageLike(
val message: Message,
val context: RuntimeContext,
) : MessageReflector {
override fun hasField(field: FieldDescriptor) =
message.hasField(field)

override fun getField(field: FieldDescriptor) =
ProtoktObjectValue(
field,
message.getField(field)!!,
context
)
}

internal class ProtoktMessageValue(
private val message: Message,
private val context: RuntimeContext,
) : Value {
override fun fieldDescriptor() =
null

override fun messageValue() =
ProtoktMessageLike(message, context)

override fun repeatedValue() =
emptyList<Value>()

override fun mapValue() =
emptyMap<Value, Value>()

override fun celValue() =
context.convertValue(message)

override fun <T : Any> jvmValue(clazz: Class<T>) =
null
}

internal class ProtoktObjectValue(
private val fieldDescriptor: FieldDescriptor,
private val value: Any,
private val context: RuntimeContext,
) : Value {
override fun fieldDescriptor() =
fieldDescriptor

override fun messageValue(): MessageReflector =
ProtoktMessageLike(value as Message, context)

override fun repeatedValue() =
(value as List<*>).map { ProtoktObjectValue(fieldDescriptor, it!!, context) }

override fun mapValue(): Map<Value, Value> {
val input = value as Map<*, *>

val keyDesc = fieldDescriptor.messageType.findFieldByNumber(1)
val valDesc = fieldDescriptor.messageType.findFieldByNumber(2)

return input.entries.associate { (key, value) ->
Pair(
ProtoktObjectValue(keyDesc, key!!, context),
ProtoktObjectValue(valDesc, value!!, context),
)
}
}

override fun celValue() =
when (value) {
is Enum -> value.value.toLong()
is Float -> value.toDouble()
is Int -> value.toLong()
is UInt -> UnsignedLong.valueOf(value.toLong())
is ULong -> UnsignedLong.valueOf(value.toLong())
is Message, is Bytes -> context.convertValue(value)

// pray
else -> value
}

override fun <T : Any> jvmValue(clazz: Class<T>): T? =
context.convertValue(value)
?.let {
when (it) {
is Int -> it.toLong()
is Float -> it.toDouble()
else -> it
}
}
?.let(clazz::cast)
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import kotlin.reflect.full.findAnnotation

@Beta
class Validator @JvmOverloads constructor(
config: Config = Config.newBuilder().build()
config: Config = Config.newBuilder().build(),
private val lazyConvert: Boolean = true
) {
private val evaluatorBuilder = ProtoktEvaluatorBuilder(config)

Expand All @@ -57,7 +58,7 @@ class Validator @JvmOverloads constructor(
val result =
evaluatorsByFullTypeName
.getValue(message::class.findAnnotation<GeneratedMessage>()!!.fullTypeName)
.evaluate(message, runtimeContext, failFast)
.evaluate(message, runtimeContext, failFast, lazyConvert)

return if (result.isEmpty()) {
ValidationResult.EMPTY
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ include(
"testing:protokt-generation-2",
"testing:protobuf-java",
"testing:protovalidate-conformance",
"testing:protovalidate-conformance:protos",
"testing:protobufjs",
"testing:testing-util",

Expand Down
39 changes: 5 additions & 34 deletions testing/protovalidate-conformance/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,17 @@
* limitations under the License.
*/

import com.google.protobuf.gradle.GenerateProtoTask
import com.google.protobuf.gradle.proto
import org.gradle.api.distribution.plugins.DistributionPlugin.TASK_INSTALL_NAME

plugins {
id("protokt.jvm-conventions")
application
}

localProtokt(false)

dependencies {
implementation(project(":protokt-protovalidate"))
implementation(project(":protokt-reflect"))
implementation(project(":testing:protovalidate-conformance:protos"))
implementation(kotlin("reflect"))
implementation(libs.cel)
implementation(libs.classgraph)
Expand All @@ -36,39 +33,10 @@ dependencies {
testImplementation(libs.truth)
}

sourceSets.main {
proto {
srcDir(project.layout.buildDirectory.file("protovalidate/export"))
}
}

val protovalidateVersion = libs.versions.protovalidate.get()
val gobin = project.layout.buildDirectory.file("gobin").get().asFile.absolutePath
val bufExecutable = project.layout.buildDirectory.file("gobin/buf").get().asFile
val conformanceExecutable = project.layout.buildDirectory.file("gobin/protovalidate-conformance").get().asFile

val installBuf =
tasks.register<Exec>("installBuf") {
environment("GOBIN", gobin)
outputs.file(bufExecutable)
commandLine("go", "install", "github.com/bufbuild/buf/cmd/buf@v${libs.versions.buf.get()}")
}

val downloadConformanceProtos =
tasks.register<Exec>("downloadConformanceProtos") {
dependsOn(installBuf)
commandLine(
bufExecutable,
"export",
"buf.build/bufbuild/protovalidate-testing:v$protovalidateVersion",
"--output=build/protovalidate/export"
)
}

tasks.withType<GenerateProtoTask> {
dependsOn(downloadConformanceProtos)
}

val installConformance =
tasks.register<Exec>("installProtovalidateConformance") {
environment("GOBIN", gobin)
Expand All @@ -80,13 +48,16 @@ val installConformance =
)
}

val lazyBufImpl: String by project

val conformance =
tasks.register<Exec>("conformance") {
dependsOn(TASK_INSTALL_NAME, installConformance)
description = "Runs protovalidate conformance tests."
environment(
"JAVA_OPTS" to "-Xmx64M",
"GOMEMLIMIT" to "40MiB"
"GOMEMLIMIT" to "40MiB",
"LAZY_BUF_IMPL" to lazyBufImpl
)
commandLine(
conformanceExecutable.absolutePath,
Expand Down
68 changes: 68 additions & 0 deletions testing/protovalidate-conformance/protos/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2025 Toast, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import com.google.protobuf.gradle.GenerateProtoTask
import com.google.protobuf.gradle.proto
import protokt.v1.gradle.ProtoktExtension

plugins {
id("protokt.jvm-conventions")
}

localProtokt(false)

sourceSets.main {
proto {
srcDir(project.layout.buildDirectory.file("protovalidate/export"))
}
}

configure<ProtoktExtension> {
generate {
// lots of protos; this would take a long time
formatOutput = false
}
}

dependencies {
implementation(libs.protobuf.java)
}

val protovalidateVersion = libs.versions.protovalidate.get()
val gobin = project.layout.buildDirectory.file("gobin").get().asFile.absolutePath
val bufExecutable = project.layout.buildDirectory.file("gobin/buf").get().asFile
val conformanceExecutable = project.layout.buildDirectory.file("gobin/protovalidate-conformance").get().asFile

val installBuf =
tasks.register<Exec>("installBuf") {
environment("GOBIN", gobin)
outputs.file(bufExecutable)
commandLine("go", "install", "github.com/bufbuild/buf/cmd/buf@v${libs.versions.buf.get()}")
}

val downloadConformanceProtos =
tasks.register<Exec>("downloadConformanceProtos") {
dependsOn(installBuf)
commandLine(
bufExecutable,
"export",
"buf.build/bufbuild/protovalidate-testing:v$protovalidateVersion",
"--output=build/protovalidate/export"
)
}

tasks.withType<GenerateProtoTask> {
dependsOn(downloadConformanceProtos)
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ object Main {
Config.newBuilder()
.setTypeRegistry(createTypeRegistry(fileDescriptors))
.setExtensionRegistry(createExtensionRegistry(fileDescriptors))
.build()
.build(),
System.getenv("LAZY_BUF_IMPL").toBoolean()
)
loadValidDescriptors(validator, descriptorMap.values)
return TestConformanceResponse
Expand Down
Loading