-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support protovalidate validation (#259)
Right now the validator eagerly converts the protokt message to a protobuf DynamicMessage. With bufbuild/protovalidate-java#132, this can [change to supply a wrapper](0cd3157) around the protokt message that only converts as requested by the validator. Not sure if this module belongs at the top level or in third-party. In that case, maybe the gRPC modules also belong in third-party. For example, supposing protokt one day supports the [Connect](https://connectrpc.com/) protocol, that would also go in third-party, but it feels like a sibling to gRPC. Fixes #207.
- Loading branch information
1 parent
a00a12b
commit db30a7d
Showing
16 changed files
with
1,188 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
public final class protokt/v1/buf/validate/Validator { | ||
public fun <init> ()V | ||
public fun <init> (Lbuild/buf/protovalidate/Config;)V | ||
public synthetic fun <init> (Lbuild/buf/protovalidate/Config;ILkotlin/jvm/internal/DefaultConstructorMarker;)V | ||
public final fun load (Lcom/google/protobuf/Descriptors$Descriptor;)V | ||
public final fun validate (Lprotokt/v1/Message;)Lbuild/buf/protovalidate/ValidationResult; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* | ||
* Copyright (c) 2024 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. | ||
*/ | ||
|
||
plugins { | ||
id("protokt.jvm-conventions") | ||
} | ||
|
||
enablePublishing() | ||
trackKotlinApiCompatibility() | ||
|
||
dependencies { | ||
implementation(project(":protokt-reflect")) | ||
implementation(kotlin("reflect")) | ||
implementation(libs.cel) | ||
implementation(libs.protovalidateJava) | ||
} |
65 changes: 65 additions & 0 deletions
65
protokt-protovalidate/src/main/kotlin/protokt/v1/buf/validate/Validator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/* | ||
* Copyright (c) 2024 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 protokt.v1.buf.validate | ||
|
||
import build.buf.protovalidate.Config | ||
import build.buf.protovalidate.ValidationResult | ||
import build.buf.protovalidate.internal.celext.ValidateLibrary | ||
import build.buf.protovalidate.internal.evaluator.Evaluator | ||
import build.buf.protovalidate.internal.evaluator.EvaluatorBuilder | ||
import build.buf.protovalidate.internal.evaluator.MessageValue | ||
import com.google.protobuf.Descriptors.Descriptor | ||
import org.projectnessie.cel.Env | ||
import org.projectnessie.cel.Library | ||
import protokt.v1.Beta | ||
import protokt.v1.GeneratedMessage | ||
import protokt.v1.Message | ||
import protokt.v1.google.protobuf.RuntimeContext | ||
import protokt.v1.google.protobuf.toDynamicMessage | ||
import java.util.Collections | ||
import java.util.concurrent.ConcurrentHashMap | ||
import kotlin.reflect.full.findAnnotation | ||
|
||
@Beta | ||
class Validator @JvmOverloads constructor( | ||
config: Config = Config.newBuilder().build() | ||
) { | ||
private val evaluatorBuilder = EvaluatorBuilder(Env.newEnv(Library.Lib(ValidateLibrary())), config) | ||
|
||
private val failFast = config.isFailFast | ||
|
||
private val evaluatorsByFullTypeName = ConcurrentHashMap<String, Evaluator>() | ||
private val descriptors = Collections.newSetFromMap(ConcurrentHashMap<Descriptor, Boolean>()) | ||
|
||
@Volatile | ||
private var runtimeContext = RuntimeContext(emptyList()) | ||
|
||
fun load(descriptor: Descriptor) { | ||
doLoad(descriptor) | ||
runtimeContext = RuntimeContext(descriptors) | ||
} | ||
|
||
private fun doLoad(descriptor: Descriptor) { | ||
descriptors.add(descriptor) | ||
evaluatorsByFullTypeName[descriptor.fullName] = evaluatorBuilder.load(descriptor) | ||
descriptor.nestedTypes.forEach(::doLoad) | ||
} | ||
|
||
fun validate(message: Message): ValidationResult = | ||
evaluatorsByFullTypeName | ||
.getValue(message::class.findAnnotation<GeneratedMessage>()!!.fullTypeName) | ||
.evaluate(MessageValue(message.toDynamicMessage(runtimeContext)), failFast) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* Copyright (c) 2024 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 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(kotlin("reflect")) | ||
implementation(libs.cel) | ||
implementation(libs.classgraph) | ||
implementation(libs.protovalidateJava) | ||
|
||
testImplementation(project(":testing:testing-util")) | ||
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) | ||
outputs.file(conformanceExecutable) | ||
commandLine( | ||
"go", | ||
"install", | ||
"github.com/bufbuild/protovalidate/tools/protovalidate-conformance@v$protovalidateVersion" | ||
) | ||
} | ||
|
||
val conformance = | ||
tasks.register<Exec>("conformance") { | ||
dependsOn(TASK_INSTALL_NAME, installConformance) | ||
description = "Runs protovalidate conformance tests." | ||
environment( | ||
"JAVA_OPTS" to "-Xmx45M", | ||
"GOMEMLIMIT" to "20MiB" | ||
) | ||
commandLine( | ||
conformanceExecutable.absolutePath, | ||
"--strict_message", | ||
"--strict_error", | ||
"--expected_failures", | ||
"expected_failures.yaml", | ||
project.layout.buildDirectory.dir("install/${project.name}/bin/${project.name}").get().asFile.absolutePath | ||
) | ||
} | ||
|
||
tasks.test { dependsOn(conformance) } | ||
|
||
application { | ||
mainClass.set("protokt.v1.buf.validate.conformance.Main") | ||
} |
Oops, something went wrong.