[json] IJ-CR-149952 Prefer allowed values list instead of regexp-based schemaId reporting

(cherry picked from commit 047b36e55b16ec4721f882c879dbb9e415511849)

IJ-CR-149952

GitOrigin-RevId: 6bf17ed7a46cc07e38aad2e3e403ce9315d4b29f
This commit is contained in:
Nikita Katkov
2024-11-25 20:55:43 +01:00
committed by intellij-monorepo-bot
parent f651b9272e
commit 883722fe8e
7 changed files with 3898 additions and 10 deletions

View File

@@ -32,5 +32,6 @@
<orderEntry type="library" name="StreamEx" level="project" />
<orderEntry type="library" name="kotlinx-collections-immutable" level="project" />
<orderEntry type="module" module-name="intellij.json.split" exported="" />
<orderEntry type="library" name="jackson-module-kotlin" level="project" />
</component>
</module>

View File

@@ -134,6 +134,7 @@
<pluginSuggestionProvider implementation="com.jetbrains.jsonSchema.wiremock.WireMockSuggestionProvider"/>
<statistics.counterUsagesCollector implementationClass="com.jetbrains.jsonSchema.fus.JsonFeatureUsageCollector"/>
<statistics.validation.customValidationRule implementation="com.jetbrains.jsonSchema.fus.JsonSchemaIdValidationRule"/>
</extensions>
<extensions defaultExtensionNs="JavaScript">

View File

@@ -4,7 +4,6 @@ package com.jetbrains.jsonSchema.fus
import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventField
import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.eventLog.events.EventFields.StringValidatedByRegexpReference
import com.intellij.internal.statistic.eventLog.events.RoundedIntEventField
import com.intellij.internal.statistic.eventLog.events.StringEventField
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
@@ -13,7 +12,7 @@ import org.jetbrains.annotations.ApiStatus
internal object JsonFeatureUsageCollector : CounterUsagesCollector() {
private val jsonSchemaGroup = EventLogGroup(
id = "json.schema.features",
version = 2,
version = 3,
)
internal val jsonSchemaHighlightingSessionData =
@@ -32,7 +31,7 @@ sealed interface JsonSchemaFusFeature {
companion object {
fun getAllRegistered(): List<EventField<*>> {
return listOf(
JsonSchemaFusRegexpFeature.entries,
JsonSchemaFusAllowedListFeature.entries,
JsonSchemaFusCountedUniqueFeature.entries,
JsonSchemaFusCountedFeature.entries
).flatten()
@@ -41,8 +40,8 @@ sealed interface JsonSchemaFusFeature {
}
}
enum class JsonSchemaFusRegexpFeature(override val event: StringEventField) : JsonSchemaFusFeature {
JsonFusSchemaId(StringValidatedByRegexpReference("schema_id", "^(https?):\\/\\/[^\\s/$.?#].[^\\s]*$", "JSON schema ID"))
enum class JsonSchemaFusAllowedListFeature(override val event: StringEventField) : JsonSchemaFusFeature {
JsonFusSchemaId(EventFields.StringValidatedByCustomRule("schema_id", JsonSchemaIdValidationRule::class.java, "JSON schema ID"))
}
enum class JsonSchemaFusCountedUniqueFeature(override val event: RoundedIntEventField) : JsonSchemaFusFeature {

View File

@@ -6,6 +6,7 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.psi.util.ReadActionCachedValue
import com.jetbrains.jsonSchema.impl.JsonSchemaObject
import org.jetbrains.annotations.ApiStatus
import java.util.concurrent.atomic.AtomicInteger
@@ -42,9 +43,9 @@ class JsonSchemaHighlightingSessionStatisticsCollector {
return currentHighlightingSession.getCachedOrEvaluate()
}
fun reportSchemaType(schemaId: String?) {
fun reportSchemaType(schemaRoot: JsonSchemaObject) {
val currentSession = getOrComputeCurrentSession() ?: return
currentSession.schemaType = schemaId
currentSession.schemaType = guessBestSchemaId(schemaRoot)
}
fun reportSchemaUsageFeature(featureKind: JsonSchemaFusFeature) {
@@ -64,7 +65,7 @@ class JsonSchemaHighlightingSessionStatisticsCollector {
.map { (feature, usagesCount) -> feature.event.with(usagesCount) }
val uniqueSchemasCount = JsonSchemaFusCountedUniqueFeature.UniqueRemoteUrlDownloadRequest.event.with(sessionData.requestedRemoteSchemas.size)
val schemaAccessOutsideHighlightingCount = JsonSchemaFusCountedUniqueFeature.SchemaAccessWithoutReadLock.event.with(requestsOutsideHighlightingCounter.getAndSet(0))
val schemaId = JsonSchemaFusRegexpFeature.JsonFusSchemaId.event.with(sessionData.schemaType)
val schemaId = JsonSchemaFusAllowedListFeature.JsonFusSchemaId.event.with(sessionData.schemaType)
val allDataAccumulated = allCountEventsDuringSession + uniqueSchemasCount + schemaAccessOutsideHighlightingCount + schemaId
JsonFeatureUsageCollector.jsonSchemaHighlightingSessionData.log(allDataAccumulated)
@@ -74,5 +75,9 @@ class JsonSchemaHighlightingSessionStatisticsCollector {
thisLogger().debug("JSON schema highlighting session statistics: $printableStatistics")
}
}
}
private fun guessBestSchemaId(schemaRoot: JsonSchemaObject): String? {
val rawSchemaIdentifier = schemaRoot.id ?: schemaRoot.rawFile?.name
return rawSchemaIdentifier?.replace("http://", "https://")
}
}

View File

@@ -0,0 +1,57 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.jsonSchema.fus
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.StreamReadFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.intellij.internal.statistic.eventLog.validator.ValidationResultType
import com.intellij.internal.statistic.eventLog.validator.rules.EventContext
import com.intellij.internal.statistic.eventLog.validator.rules.impl.CustomValidationRule
import com.intellij.openapi.diagnostic.thisLogger
import java.io.IOException
/**
* All known schema ids and names downloaded from https://schemastore.org/api/json/catalog.json
* The list is quite big, so it is extracted to a separate resource file and must be loaded outside EDT to prevent possible freezes.
*/
internal class JsonSchemaIdValidationRule : CustomValidationRule() {
override fun getRuleId(): String = "json_schema_id_rule"
override fun doValidate(data: String, context: EventContext): ValidationResultType {
return if (AllowListHolder.allowedNames.contains(data)) ValidationResultType.ACCEPTED else ValidationResultType.REJECTED
}
object AllowListHolder {
val allowedNames: Set<String> by lazy {
deserialiseBundledAllowedSchemaIds()
}
fun deserialiseBundledAllowedSchemaIds(): Set<String> {
val bundledDataStream =
try {
JsonSchemaIdValidationRule::class.java.getResourceAsStream("KnownSchemaIdentifiers.json").use { stream ->
val objectMapper = ObjectMapper(
JsonFactory.builder().enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION).build()
)
objectMapper.readValue<ArrayList<KnownJsonSchemaIdentity>>(
stream,
objectMapper.typeFactory.constructCollectionType(ArrayList::class.java, KnownJsonSchemaIdentity::class.java)
)
}
}
catch (exception: IOException) {
thisLogger().warn("Failed to load bundled allowed schema identifiers", exception)
return emptySet()
}
return bundledDataStream
.asSequence()
.flatMap { sequenceOf(it.url, it.fileName).filterNotNull() }
.toSet()
}
@JsonIgnoreProperties(ignoreUnknown = true)
data class KnownJsonSchemaIdentity(val url: String? = null, val fileName: String? = null)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -59,7 +59,7 @@ public final class JsonSchemaComplianceChecker {
}
public void annotate(final @NotNull PsiElement element) {
JsonSchemaHighlightingSessionStatisticsCollector.getInstance().reportSchemaType(myRootSchema.getId());
JsonSchemaHighlightingSessionStatisticsCollector.getInstance().reportSchemaType(myRootSchema);
doAnnotate(element);
JsonSchemaHighlightingSessionStatisticsCollector.getInstance().flushHighlightingSessionDataToFus();
}