Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -333,18 +333,18 @@ class Options(
// When the key isn't a `ProtoMember`, this key/value pair is a inlined value of a map
// field. We don't need to track the key type in map fields for they are always of
// scalar types. We however have to check the value type.
gatherFields(sink, type, value!!, pruningRules)
gatherFields(sink, type, value, pruningRules)
continue
}
}
if (pruningRules.prunes(protoMember)) continue
sink.getOrPut(type, ::ArrayList).add(protoMember)
gatherFields(sink, protoMember.type, value!!, pruningRules)
gatherFields(sink, protoMember.type, value, pruningRules)
}
}
is List<*> -> {
for (e in o) {
gatherFields(sink, type, e!!, pruningRules)
gatherFields(sink, type, e, pruningRules)
}
}
}
Expand Down Expand Up @@ -373,9 +373,11 @@ class Options(
schema: Schema,
markSet: MarkSet,
type: ProtoType?,
o: Any,
o: Any?,
): Any? {
return when {
o == null -> null

o is Map<*, *> -> {
val map = mutableMapOf<ProtoMember, Any>()
for ((key, value) in o) {
Expand All @@ -384,7 +386,7 @@ class Options(
else -> {
// When the key isn't a `ProtoMember`, this key/value pair is a inlined value of a map
// field.
val retainedValue = retainAll(schema, markSet, type, value!!)
val retainedValue = retainAll(schema, markSet, type, value)
// if `retainedValue` is a map, its value represents an inline message, and we need to
// mark the proto member.
if (retainedValue is Map<*, *>) {
Expand All @@ -403,11 +405,11 @@ class Options(
}

val field = schema.getField(protoMember)!!
val retainedValue = retainAll(schema, markSet, field.type, value!!)
val retainedValue = retainAll(schema, markSet, field.type, value)
if (retainedValue != null) {
map[protoMember] = retainedValue // This retained field is non-empty.
} else if (isCoreMemberOfGoogleProtobuf) {
map[protoMember] = value
map[protoMember] = value!!
}
}
map.ifEmpty { null }
Expand All @@ -416,7 +418,7 @@ class Options(
o is List<*> -> {
val list = mutableListOf<Any>()
for (value in o) {
val retainedValue = retainAll(schema, markSet, type, value!!)
val retainedValue = retainAll(schema, markSet, type, value)
if (retainedValue != null) {
list.add(retainedValue) // This retained value is non-empty.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,87 @@ class PrunerTest {
)
}

/**
* Map option entries may omit 'value' when the value type's default is desired. This is common
* in OpenAPI v2 annotations, e.g. a security requirement that needs bearer auth but no scopes:
*
* ```
* security: { security_requirement: { key: "bearer" } }
* ```
*
* The missing 'value' means an empty SecurityRequirementValue (no scopes). Wire must handle
* these null map values without crashing during pruning.
*/
@Test
fun rootCanHandleInlinedOptionWithMapFieldsMissingValue() {
val schema = buildSchema {
add(
"openapiv2.proto".toPath(),
"""
|syntax = "proto3";
|
|import "google/protobuf/descriptor.proto";
|
|package grpc.gateway.protoc_gen_openapiv2.options;
|
|message SecurityRequirement {
| message SecurityRequirementValue {
| repeated string scope = 1;
| }
| map<string, SecurityRequirementValue> security_requirement = 1;
|}
|
|message Swagger {
| repeated SecurityRequirement security = 1;
|}
|
|extend google.protobuf.FileOptions {
| Swagger openapiv2_swagger = 80000;
|}
""".trimMargin(),
)
add(
"test.proto".toPath(),
"""
|syntax = "proto3";
|
|import "openapiv2.proto";
|
|package test;
|
|option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
| security: { security_requirement: { key: "bearer" } }
|};
""".trimMargin(),
)
}
val pruned = schema.prune(
PruningRules.Builder()
.addRoot("grpc.gateway.protoc_gen_openapiv2.options.SecurityRequirement#security_requirement")
.build(),
)
assertThat(pruned.protoFile("openapiv2.proto")!!.toSchema())
.isEqualTo(
"""
|// Proto schema formatted by Wire, do not edit.
|// Source: openapiv2.proto
|
|syntax = "proto3";
|
|package grpc.gateway.protoc_gen_openapiv2.options;
|
|message SecurityRequirement {
| map<string, SecurityRequirementValue> security_requirement = 1;
|
| message SecurityRequirementValue {
| repeated string scope = 1;
| }
|}
|
""".trimMargin(),
)
}

@Ignore("Pruning inlined map options is not supported")
@Test
fun pruneCanHandleInlinedOptionMemberWithMapFields() {
Expand Down
Loading