Skip to content

Commit 8b7253e

Browse files
matejdroBraisGabin
andauthored
Allow merging sarifs with mismatching schema and/or version (#223)
* Allow merging sarifs with mismatching schema and/or version * allow specifying which schema to use in the config * fix copy and paste docs * Update src/jvmTest/kotlin/io/github/detekt/sarif4k/SarifMergingTest.kt --------- Co-authored-by: Brais Gabín <braisgabin@gmail.com>
1 parent 43c95ad commit 8b7253e

File tree

7 files changed

+500
-7
lines changed

7 files changed

+500
-7
lines changed

api/sarif4k.api

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,15 @@ public final class io/github/detekt/sarif4k/LogicalLocation$Companion {
10021002

10031003
public final class io/github/detekt/sarif4k/Merging {
10041004
public static final fun merge (Lio/github/detekt/sarif4k/SarifSchema210;Lio/github/detekt/sarif4k/SarifSchema210;)Lio/github/detekt/sarif4k/SarifSchema210;
1005+
public static final fun merge (Lio/github/detekt/sarif4k/SarifSchema210;Lio/github/detekt/sarif4k/SarifSchema210;Lio/github/detekt/sarif4k/MergingConfig;)Lio/github/detekt/sarif4k/SarifSchema210;
1006+
}
1007+
1008+
public final class io/github/detekt/sarif4k/MergingConfig {
1009+
public fun <init> ()V
1010+
public fun <init> (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V
1011+
public synthetic fun <init> (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
1012+
public final fun getSelectSchema ()Lkotlin/jvm/functions/Function2;
1013+
public final fun getSelectVersion ()Lkotlin/jvm/functions/Function2;
10051014
}
10061015

10071016
public final class io/github/detekt/sarif4k/Message {

api/sarif4k.klib.api

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,15 @@ final class io.github.detekt.sarif4k/LogicalLocation { // io.github.detekt.sarif
12971297
}
12981298
}
12991299

1300+
final class io.github.detekt.sarif4k/MergingConfig { // io.github.detekt.sarif4k/MergingConfig|null[0]
1301+
constructor <init>(kotlin/Function2<kotlin/String?, kotlin/String?, kotlin/String?> = ..., kotlin/Function2<io.github.detekt.sarif4k/Version, io.github.detekt.sarif4k/Version, io.github.detekt.sarif4k/Version> = ...) // io.github.detekt.sarif4k/MergingConfig.<init>|<init>(kotlin.Function2<kotlin.String?,kotlin.String?,kotlin.String?>;kotlin.Function2<io.github.detekt.sarif4k.Version,io.github.detekt.sarif4k.Version,io.github.detekt.sarif4k.Version>){}[0]
1302+
1303+
final val selectSchema // io.github.detekt.sarif4k/MergingConfig.selectSchema|{}selectSchema[0]
1304+
final fun <get-selectSchema>(): kotlin/Function2<kotlin/String?, kotlin/String?, kotlin/String?> // io.github.detekt.sarif4k/MergingConfig.selectSchema.<get-selectSchema>|<get-selectSchema>(){}[0]
1305+
final val selectVersion // io.github.detekt.sarif4k/MergingConfig.selectVersion|{}selectVersion[0]
1306+
final fun <get-selectVersion>(): kotlin/Function2<io.github.detekt.sarif4k/Version, io.github.detekt.sarif4k/Version, io.github.detekt.sarif4k/Version> // io.github.detekt.sarif4k/MergingConfig.selectVersion.<get-selectVersion>|<get-selectVersion>(){}[0]
1307+
}
1308+
13001309
final class io.github.detekt.sarif4k/Message { // io.github.detekt.sarif4k/Message|null[0]
13011310
constructor <init>(kotlin.collections/List<kotlin/String>? = ..., kotlin/String? = ..., kotlin/String? = ..., io.github.detekt.sarif4k/PropertyBag? = ..., kotlin/String? = ...) // io.github.detekt.sarif4k/Message.<init>|<init>(kotlin.collections.List<kotlin.String>?;kotlin.String?;kotlin.String?;io.github.detekt.sarif4k.PropertyBag?;kotlin.String?){}[0]
13021311

@@ -2823,3 +2832,4 @@ final object io.github.detekt.sarif4k/SarifSerializer { // io.github.detekt.sari
28232832
}
28242833

28252834
final fun (io.github.detekt.sarif4k/SarifSchema210).io.github.detekt.sarif4k/merge(io.github.detekt.sarif4k/SarifSchema210): io.github.detekt.sarif4k/SarifSchema210 // io.github.detekt.sarif4k/merge|merge@io.github.detekt.sarif4k.SarifSchema210(io.github.detekt.sarif4k.SarifSchema210){}[0]
2835+
final fun (io.github.detekt.sarif4k/SarifSchema210).io.github.detekt.sarif4k/merge(io.github.detekt.sarif4k/SarifSchema210, io.github.detekt.sarif4k/MergingConfig): io.github.detekt.sarif4k/SarifSchema210 // io.github.detekt.sarif4k/merge|merge@io.github.detekt.sarif4k.SarifSchema210(io.github.detekt.sarif4k.SarifSchema210;io.github.detekt.sarif4k.MergingConfig){}[0]

src/commonMain/kotlin/io/github/detekt/sarif4k/Merging.kt

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ package io.github.detekt.sarif4k
44

55
import kotlin.jvm.JvmName
66

7-
public fun SarifSchema210.merge(other: SarifSchema210): SarifSchema210 {
8-
require(schema == other.schema) { "Cannot merge sarifs with different schemas: '$schema' or '${other.schema}'" }
9-
require(version == other.version) { "Cannot merge sarifs with different versions: '$version' or '${other.version}'" }
7+
public fun SarifSchema210.merge(other: SarifSchema210, config: MergingConfig): SarifSchema210 {
108

119
val mergedExternalProperties = (inlineExternalProperties.orEmpty() + other.inlineExternalProperties.orEmpty())
1210
.takeIf { it.isNotEmpty() }
@@ -21,14 +19,18 @@ public fun SarifSchema210.merge(other: SarifSchema210): SarifSchema210 {
2119
val mergedRuns = runs.merge(other.runs)
2220

2321
return SarifSchema210(
24-
schema,
25-
version,
22+
config.selectSchema(schema, other.schema),
23+
config.selectVersion(version, other.version),
2624
mergedExternalProperties,
2725
mergedProperties,
2826
mergedRuns
2927
)
3028
}
3129

30+
public fun SarifSchema210.merge(other: SarifSchema210): SarifSchema210 {
31+
return merge(other, MergingConfig())
32+
}
33+
3234
private fun PropertyBag.merge(other: PropertyBag): PropertyBag {
3335
val aTags = this["tags"] as? Collection<*>
3436
val bTags = other["tags"] as? Collection<*>
@@ -83,6 +85,7 @@ private fun mergeOriginalURIBaseIDs(runs: List<Run>): Pair<Map<String, ArtifactL
8385
mergedMap[key] = artifactLocation
8486
key
8587
}
88+
8689
artifactLocation -> key
8790
else -> {
8891
val newKey = generateSequence("${key}_${keyCounter}") { "${key}_${++keyCounter}" }
@@ -117,3 +120,28 @@ private fun Result.updateArtifactLocationBaseIds(keyMappings: Map<String, String
117120

118121
return copy(locations = updatedLocations)
119122
}
123+
124+
public class MergingConfig(
125+
/**
126+
* Select the schema of the merged sarif. Function receives schemas of both sarifs as an input and must return
127+
* a valid schema that gets outputted into the merged sarif.
128+
*/
129+
public val selectSchema: (String?, String?) -> String? = { first, second ->
130+
if (first != second) {
131+
throw IllegalArgumentException("Cannot merge sarifs with different schemas: '$first' or '${second}'")
132+
} else {
133+
first
134+
}
135+
},
136+
/**
137+
* Select the version of the merged sarif. Function receives versions of both sarifs as an input and must return
138+
* a valid version that gets outputted into the merged sarif.
139+
*/
140+
public val selectVersion: (Version, Version) -> Version = { first, second ->
141+
if (first != second) {
142+
throw IllegalArgumentException("Cannot merge sarifs with different versions: '$first' or '${second}'")
143+
} else {
144+
first
145+
}
146+
},
147+
)

src/jvmTest/kotlin/io/github/detekt/sarif4k/SarifMergingTest.kt

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.github.detekt.sarif4k
33
import io.github.detekt.sarif4k.util.resourceAsTextContent
44
import org.junit.jupiter.api.Test
55
import kotlin.test.assertEquals
6+
import org.junit.jupiter.api.assertThrows
67

78
class SarifMergingTest {
89
@Test
@@ -32,12 +33,58 @@ class SarifMergingTest {
3233
)
3334
}
3435

35-
private fun testMerge(expectedOutputFile: String, vararg inputFiles: String) {
36+
@Test
37+
fun `fail merging by default when the schema does not match`() {
38+
assertThrows<IllegalArgumentException> {
39+
testMerge(
40+
"output.sarif.json",
41+
"input_1.sarif.json",
42+
"input_2_different_schema.sarif.json"
43+
)
44+
}
45+
}
46+
47+
@Test
48+
fun `fail merging by default when the version does not match`() {
49+
assertThrows<IllegalArgumentException> {
50+
testMerge(
51+
"output.sarif.json",
52+
"input_1.sarif.json",
53+
"input_2_different_version.sarif.json"
54+
)
55+
}
56+
}
57+
58+
@Test
59+
fun `allow mismatched schema when specified in the config`() {
60+
testMerge(
61+
"output.sarif.json",
62+
"input_1.sarif.json",
63+
"input_2_different_schema.sarif.json",
64+
config = MergingConfig(selectSchema = { a, _ -> a })
65+
)
66+
}
67+
68+
@Test
69+
fun `select schema of the second sarif file via config`() {
70+
testMerge(
71+
"output_different_schema.sarif.json",
72+
"input_1.sarif.json",
73+
"input_2_different_schema.sarif.json",
74+
config = MergingConfig(selectSchema = { _, b -> b })
75+
)
76+
}
77+
78+
private fun testMerge(
79+
expectedOutputFile: String,
80+
vararg inputFiles: String,
81+
config: MergingConfig = MergingConfig()
82+
) {
3683
val inputs = inputFiles.map {
3784
SarifSerializer.fromJson(resourceAsTextContent(it))
3885
}
3986

40-
val actual = inputs.reduce(SarifSchema210::merge)
87+
val actual = inputs.reduce { schema, other -> schema.merge(other, config) }
4188
val expected = SarifSerializer.fromJson(resourceAsTextContent(expectedOutputFile))
4289
assertEquals(expected, actual)
4390
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
{
2+
"$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema2-2.1.0.json",
3+
"version": "2.1.0",
4+
"runs": [
5+
{
6+
"originalUriBaseIds": {
7+
"%SRCROOT%": {
8+
"uri": "file:///Users/tester/detekt/"
9+
}
10+
},
11+
"results": [
12+
{
13+
"level": "warning",
14+
"locations": [
15+
{
16+
"physicalLocation": {
17+
"artifactLocation": {
18+
"uri": "TestFile.kt",
19+
"uriBaseId": "%SRCROOT%"
20+
},
21+
"region": {
22+
"startColumn": 1,
23+
"startLine": 1
24+
}
25+
}
26+
}
27+
],
28+
"message": {
29+
"text": "TestMessage"
30+
},
31+
"ruleId": "detekt.TestSmellD.TestSmellD"
32+
},
33+
{
34+
"level": "warning",
35+
"locations": [
36+
{
37+
"physicalLocation": {
38+
"artifactLocation": {
39+
"uri": "TestFile.kt",
40+
"uriBaseId": "%SRCROOT%"
41+
},
42+
"region": {
43+
"startColumn": 1,
44+
"startLine": 1
45+
}
46+
}
47+
}
48+
],
49+
"message": {
50+
"text": "TestMessage"
51+
},
52+
"ruleId": "detekt.TestSmellE.TestSmellE"
53+
},
54+
{
55+
"level": "warning",
56+
"locations": [
57+
{
58+
"physicalLocation": {
59+
"artifactLocation": {
60+
"uri": "TestFile.kt",
61+
"uriBaseId": "%SRCROOT%"
62+
},
63+
"region": {
64+
"startColumn": 1,
65+
"startLine": 1
66+
}
67+
}
68+
}
69+
],
70+
"message": {
71+
"text": "TestMessage"
72+
},
73+
"ruleId": "detekt.TestSmellF.TestSmellF"
74+
}
75+
],
76+
"tool": {
77+
"driver": {
78+
"downloadUri": "https://github.com/detekt/detekt/releases/download/v1.0.0/detekt",
79+
"fullName": "detekt",
80+
"guid": "022ca8c2-f6a2-4c95-b107-bb72c43263f3",
81+
"informationUri": "https://detekt.dev",
82+
"language": "en",
83+
"name": "detekt",
84+
"organization": "detekt",
85+
"rules": [
86+
{
87+
"helpUri": "https://detekt.dev/potential-bugs.html#deprecation",
88+
"id": "detekt.potential-bugs.Deprecation",
89+
"name": "Deprecation",
90+
"shortDescription": {
91+
"text": "NoDeprecated"
92+
}
93+
}
94+
],
95+
"semanticVersion": "1.0.0",
96+
"version": "1.0.0"
97+
}
98+
}
99+
}
100+
],
101+
"properties": {
102+
"tags": [
103+
"tag",
104+
"tag2"
105+
],
106+
"bar": 5
107+
}
108+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
{
2+
"$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
3+
"version": "2.1.1",
4+
"runs": [
5+
{
6+
"originalUriBaseIds": {
7+
"%SRCROOT%": {
8+
"uri": "file:///Users/tester/detekt/"
9+
}
10+
},
11+
"results": [
12+
{
13+
"level": "warning",
14+
"locations": [
15+
{
16+
"physicalLocation": {
17+
"artifactLocation": {
18+
"uri": "TestFile.kt",
19+
"uriBaseId": "%SRCROOT%"
20+
},
21+
"region": {
22+
"startColumn": 1,
23+
"startLine": 1
24+
}
25+
}
26+
}
27+
],
28+
"message": {
29+
"text": "TestMessage"
30+
},
31+
"ruleId": "detekt.TestSmellD.TestSmellD"
32+
},
33+
{
34+
"level": "warning",
35+
"locations": [
36+
{
37+
"physicalLocation": {
38+
"artifactLocation": {
39+
"uri": "TestFile.kt",
40+
"uriBaseId": "%SRCROOT%"
41+
},
42+
"region": {
43+
"startColumn": 1,
44+
"startLine": 1
45+
}
46+
}
47+
}
48+
],
49+
"message": {
50+
"text": "TestMessage"
51+
},
52+
"ruleId": "detekt.TestSmellE.TestSmellE"
53+
},
54+
{
55+
"level": "warning",
56+
"locations": [
57+
{
58+
"physicalLocation": {
59+
"artifactLocation": {
60+
"uri": "TestFile.kt",
61+
"uriBaseId": "%SRCROOT%"
62+
},
63+
"region": {
64+
"startColumn": 1,
65+
"startLine": 1
66+
}
67+
}
68+
}
69+
],
70+
"message": {
71+
"text": "TestMessage"
72+
},
73+
"ruleId": "detekt.TestSmellF.TestSmellF"
74+
}
75+
],
76+
"tool": {
77+
"driver": {
78+
"downloadUri": "https://github.com/detekt/detekt/releases/download/v1.0.0/detekt",
79+
"fullName": "detekt",
80+
"guid": "022ca8c2-f6a2-4c95-b107-bb72c43263f3",
81+
"informationUri": "https://detekt.dev",
82+
"language": "en",
83+
"name": "detekt",
84+
"organization": "detekt",
85+
"rules": [
86+
{
87+
"helpUri": "https://detekt.dev/potential-bugs.html#deprecation",
88+
"id": "detekt.potential-bugs.Deprecation",
89+
"name": "Deprecation",
90+
"shortDescription": {
91+
"text": "NoDeprecated"
92+
}
93+
}
94+
],
95+
"semanticVersion": "1.0.0",
96+
"version": "1.0.0"
97+
}
98+
}
99+
}
100+
],
101+
"properties": {
102+
"tags": [
103+
"tag",
104+
"tag2"
105+
],
106+
"bar": 5
107+
}
108+
}

0 commit comments

Comments
 (0)