Skip to content

feat(feature-flag): add FeatureFlagPlugin with JSON schema validator - #11346

Open
rafaeltonholo wants to merge 10 commits into
thunderbird:mainfrom
rafaeltonholo:feat/11326/catalog-validation-gradle-plugin
Open

feat(feature-flag): add FeatureFlagPlugin with JSON schema validator#11346
rafaeltonholo wants to merge 10 commits into
thunderbird:mainfrom
rafaeltonholo:feat/11326/catalog-validation-gradle-plugin

Conversation

@rafaeltonholo

@rafaeltonholo rafaeltonholo commented Jul 30, 2026

Copy link
Copy Markdown
Member

Contribution Summary

Linked Issue/Ticket: Resolves #11326
RFC / Technical Design (if applicable): RFC 0004: Add a Declarative Feature Flag Catalog / Technical Design 0002: Declarative Feature Flag Catalog

Description

  • Add SchemaValidator for validating JSON catalogs against JSON schemas
  • Add FeatureFlagPlugin to validate feature flag catalog at configuration time
  • Configure plugin in root build script with catalog and schema files
  • Add networknt json-schema-validator dependency

AI Disclosure

Select one of the following (mandatory)

  • This contribution does not include any changes created or assisted by AI.
  • This contribution includes changes assisted by AI.
  • This contribution includes changes created by AI.

Contribution Checklist

  • I have read and affirm that my contribution adheres to Mozilla’s Community Participation Guidelines
  • This contribution is in Kotlin where possible
  • This contribution does not use merge commits
  • This contribution adheres to the existing codestyle (run gradlew spotlessCheck to check and gradlew spotlessApply to format your source code; will be checked by CI).
  • This contribution does not break existing unit tests (run gradlew testDebugUnitTest; will be checked by CI).
  • This contribution includes tests for any new functionality, and maintains tests for any updated functionality.
  • This contribution adheres to our Engineering process (RFC/Technical Design/ADR)
  • This PR has a descriptive title and body that accurately outlines all changes made, and contains a reference to any issues that it fixes (e.g. Closes #XXX or Fixes #XXX).

@rafaeltonholo
rafaeltonholo requested a review from a team as a code owner July 30, 2026 19:04
@rafaeltonholo rafaeltonholo added the report: exclude Exclude changes from user-facing reports (internal, minor, or not relevant to users). label Jul 30, 2026
@rafaeltonholo
rafaeltonholo requested review from dani-zilla and a lite review from Copilot July 30, 2026 19:04
@rafaeltonholo rafaeltonholo added the pr: stacked Must be used on a PR that is stacked on top of other(s) label Jul 30, 2026
@rafaeltonholo
rafaeltonholo force-pushed the feat/11326/catalog-validation-gradle-plugin branch from 32b6962 to 65ee9c0 Compare July 30, 2026 19:04
@github-actions github-actions Bot added the tb-team Tasks and features handled by project maintainers label Jul 30, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new Gradle plugin in :build-plugin:plugin to validate the declarative feature flag catalog against a JSON Schema at root-project configuration time, and wires it up in the root build so the repository catalog/schema under config/featureflag/ are validated during builds.

Changes:

  • Added net.thunderbird.gradle.plugin.featureflag Gradle plugin plus a SchemaValidator backed by com.networknt:json-schema-validator.
  • Added the feature flag schema and catalog files under config/featureflag/, and configured the root build to validate them.
  • Updated related RFC/technical design documentation status/links to reflect acceptance and the new schema name.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
gradle/libs.versions.toml Adds the NetworkNT JSON schema validator dependency and registers the new build plugin alias.
docs/SUMMARY.md Moves technical design 0002 into the “Accepted” section.
docs/engineering/technical-designs/0002-feature-flag-declarative-catalog.md Updates technical design status to Accepted.
docs/engineering/rfcs/0004-feature-flag-new-architecture.md Updates the example catalog’s $schema filename to match the shipped schema.
config/featureflag/thunderbird_mobile_featureflag.schema.json Adds the JSON Schema used to validate the feature flag catalog.
config/featureflag/thunderbird_mobile_featureflag.catalog.json Adds the repository’s feature flag catalog to be validated at build time.
build.gradle.kts Applies and configures the feature flag Gradle plugin in the root build.
build-plugin/plugin/src/test/kotlin/net/thunderbird/gradle/plugin/testing/rule/ProjectTempFolderRule.kt Adds a JUnit rule helper for writing temp project files in plugin tests.
build-plugin/plugin/src/test/kotlin/net/thunderbird/gradle/plugin/featureflag/schema/SchemaValidatorTest.kt Adds unit tests for schema validation behavior (missing files, multiple violations, format assertions).
build-plugin/plugin/src/test/kotlin/net/thunderbird/gradle/plugin/featureflag/FeatureFlagPluginTest.kt Adds Gradle plugin tests for extension registration and configuration-time validation failures/success.
build-plugin/plugin/src/test/kotlin/net/thunderbird/gradle/plugin/featureflag/fake/FakeData.kt Provides fake schema/catalog fixtures for validator/plugin tests.
build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/featureflag/schema/SchemaValidator.kt Implements JSON Schema validation using NetworkNT’s validator.
build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/featureflag/FeatureFlagPluginExtension.kt Adds the root-project featureFlag extension (schema, catalog, validateFormats).
build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/featureflag/FeatureFlagPlugin.kt Implements plugin application and configuration-time validation/error reporting.
build-plugin/plugin/build.gradle.kts Registers the new Gradle plugin ID and adds required dependencies for implementation/tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings August 4, 2026 12:44
@rafaeltonholo
rafaeltonholo force-pushed the feat/11326/catalog-validation-gradle-plugin branch from 65ee9c0 to 9486e5b Compare August 4, 2026 12:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 2 comments.

Suppressed comments (2)

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/featureflag/validator/CatalogValidator.kt:32

  • keyRegistry is built via catalog.flags.associate { it.key to it.default }, which silently drops duplicate flag keys (last one wins). The JSON schema only has uniqueItems: true for the flags array, which doesn't guarantee key uniqueness, so a catalog with duplicate keys could pass schema validation but produce ambiguous defaults/override validation.

Consider explicitly detecting duplicate flags[].key values and failing validation with a clear error message listing the duplicates.

    fun validate(catalog: FeatureFlagCatalog): Result {
        val keyRegistry = catalog.flags.associate { it.key to it.default }
        val tbOverrides = catalog.overrides.thunderbird

build-plugin/plugin/src/test/kotlin/net/thunderbird/gradle/plugin/featureflag/validator/SchemaValidatorTest.kt:169

  • Acceptance criteria require tests for rejecting an unknown application and an unknown build type in overrides. Current validator/plugin tests cover unknown override keys, but there isn't a test asserting schema validation fails for e.g. overrides.unknown_app or overrides.thunderbird.unknown_build.

Add a SchemaValidator test using catalogSchemaFile() (real schema) and a catalog JSON containing both cases, then assert the returned errors mention additionalProperties/unknown property for the offending paths.

    @Test
    fun `validate should return Success when override keys match the flag key format`() {
        // Arrange
        val schemaFile = catalogSchemaFile()
        val catalogAsText = FakeData.catalogWithOverrideKey("archive_marks_as_read")

Copilot AI review requested due to automatic review settings August 4, 2026 13:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (3)

build-plugin/plugin/src/test/kotlin/net/thunderbird/gradle/plugin/featureflag/fake/FakeData.kt:3

  • This file imports FakeData.SCHEMA from itself, but the import is unused. Because :build-plugin:plugin sets allWarningsAsErrors = true, this will fail compilation.
import net.thunderbird.gradle.plugin.featureflag.fake.FakeData.SCHEMA

build-plugin/plugin/src/test/kotlin/net/thunderbird/gradle/plugin/featureflag/validator/SchemaValidatorTest.kt:176

  • The linked acceptance criteria (#11326) explicitly call for tests covering “Unknown application” and “Unknown build type”. Current schema validation tests cover required properties, additional properties at the root, key formats, and date formats, but don’t assert that the real shipped schema rejects unexpected entries under overrides (e.g., overrides.unknownApp or overrides.thunderbird.unknownBuildType). Please add unit tests validating these cases against config/featureflag/thunderbird_mobile_featureflag.schema.json (similar to the existing override-key-format tests).
    @Test
    fun `validate should return Success when override keys match the flag key format`() {
        // Arrange
        val schemaFile = catalogSchemaFile()
        val catalogAsText = FakeData.catalogWithOverrideKey("archive_marks_as_read")
        val testSubject = SchemaValidator(validateFormats = true)

        // Act
        val result = testSubject.validate(schemaFile = schemaFile, catalogAsText = catalogAsText)

        // Assert
        assertThat(result).isEqualTo(SchemaValidator.Result.Success)

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/featureflag/validator/SchemaValidator.kt:27

  • The KDoc says this checks whether “both files exist” and that FileNotFound is returned if either file is missing, but SchemaValidator only checks the schema file (the catalog is passed as text). This can mislead future callers about what is validated here.
     * Checks if both files exist, loads the schema, and validates the catalog content against it.
     * Format assertions are enabled or disabled based on the validator configuration.
     *
     * @param schemaFile The JSON schema file to validate against
     * @param catalogAsText The JSON catalog as text to validate
     * @return Result.Success if validation passes, Result.Error.FileNotFound if either file doesn't exist,
     * or Result.Error.ValidationFailed if the catalog doesn't conform to the schema

Copilot AI review requested due to automatic review settings August 4, 2026 15:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/featureflag/FeatureFlagPlugin.kt:48

  • Error message contains a double space ("The feature flag schema"), which looks like a typo and will leak into build failures.
            "Failed to apply feature flag plugin. Reason: The  feature flag schema was not found at '${schema.path}'.",

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/featureflag/validator/SchemaValidator.kt:23

  • KDoc for validate() mentions a Result.Error.FileNotFound variant that doesn't exist in SchemaValidator.Result (and the method takes contents, not files). This is misleading documentation.
     * @return Result.Success if validation passes, Result.Error.FileNotFound if either file doesn't exist,
     * or Result.Error.ValidationFailed if the catalog doesn't conform to the schema

build-plugin/plugin/src/test/kotlin/net/thunderbird/gradle/plugin/featureflag/FeatureFlagPluginTest.kt:139

  • The new schema enforces known applications/build types via additionalProperties=false, but the test suite here doesn't cover the failure cases required by #11326 (e.g., unknown application under overrides, unknown build type under an app). Adding these cases would ensure schema validation behavior is exercised end-to-end by the plugin tests.
    @Test

Copilot AI review requested due to automatic review settings August 4, 2026 17:03
@rafaeltonholo
rafaeltonholo force-pushed the feat/11326/catalog-validation-gradle-plugin branch from 06a0a53 to bca8bbe Compare August 4, 2026 17:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Suppressed comments (2)

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/featureflag/FeatureFlagPlugin.kt:44

  • The catalog-missing error message here doesn't match the plugin tests (FeatureFlagPluginTest expects "Failed to apply feature flag plugin. Reason: The catalog file '' not found."). As-is, the test should fail even though the behavior is correct.
        val catalog = extension.catalog.asFile.get()
        val catalogContents = readTextOrNull(extension.catalog) ?: throw GradleException(
            "Failed to apply feature flag plugin. Reason: The feature flag catalog was not found at '${catalog.path}'.",
        )

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/featureflag/validator/SchemaValidator.kt:24

  • This KDoc mentions Result.Error.FileNotFound / Result.Error.ValidationFailed, but SchemaValidator.Result has only Success and ValidationFailed (and no nested Error type). This is misleading documentation for callers and for future maintenance.
     * @param schemaContents The JSON schema file contents to validate against
     * @param catalogContents The JSON catalog file contents as text to validate
     * @return Result.Success if validation passes, Result.Error.FileNotFound if either file doesn't exist,
     * or Result.Error.ValidationFailed if the catalog doesn't conform to the schema
     */

Comment thread build-plugin/plugin/build.gradle.kts Outdated
Comment on lines +16 to +20
@@ -16,6 +17,11 @@ kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_21
}
sourceSets {
@rafaeltonholo rafaeltonholo removed the pr: stacked Must be used on a PR that is stacked on top of other(s) label Aug 4, 2026
- Add SchemaValidator for validating JSON catalogs against JSON schemas
- Add FeatureFlagPlugin to validate feature flag catalog at configuration time
- Configure plugin in root build script with catalog and schema files
- Add networknt json-schema-validator dependency
- Validate that all override keys reference existing flags
- Validate that overrides differ from their flag defaults
- Report missing keys and redundant values per override
- Support both Thunderbird and K-9 Mail override structures
- Pass catalog as text instead of File to SchemaValidator
- Add CatalogValidator call after schema validation in plugin
- Introduce catalog() builder function in FakeData for test maintainability
- Add test coverage for unknown override key validation error
- Simplify test setup by reducing File I/O operations
…reading

- Read files through providers.fileContents() to track as configuration cache inputs
- Pass file contents as strings instead of File objects to validators
- Remove FileNotFound result type from SchemaValidator as file existence is checked upfront
- Improve error messages to clarify when catalog or schema files are not found
- Replace context receivers with explicit map parameter passing
- No longer needed as context receivers were removed from CatalogValidator
- Remove double space in schema error message
- Update test expectations to match corrected error messages
Copilot AI review requested due to automatic review settings August 5, 2026 18:25
@rafaeltonholo
rafaeltonholo force-pushed the feat/11326/catalog-validation-gradle-plugin branch from bca8bbe to 39666a6 Compare August 5, 2026 18:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (2)

build-plugin/plugin/src/test/kotlin/net/thunderbird/gradle/plugin/featureflag/fake/FakeData.kt:3

  • This import is unused (the KDoc links to SCHEMA in the same object without needing an import) and will fail compilation because this module sets allWarningsAsErrors = true.
import net.thunderbird.gradle.plugin.featureflag.fake.FakeData.SCHEMA

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/featureflag/validator/SchemaValidator.kt:24

  • The KDoc for validate() mentions Result.Error.FileNotFound / Result.Error.ValidationFailed, but Result only defines Success and ValidationFailed, and this method validates string contents (not files). This documentation is misleading for callers.
     * Validates a JSON catalog file against a JSON schema file.
     *
     * @param schemaContents The JSON schema file contents to validate against
     * @param catalogContents The JSON catalog file contents as text to validate
     * @return Result.Success if validation passes, Result.Error.FileNotFound if either file doesn't exist,
     * or Result.Error.ValidationFailed if the catalog doesn't conform to the schema
     */

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

report: exclude Exclude changes from user-facing reports (internal, minor, or not relevant to users). tb-team Tasks and features handled by project maintainers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Flag — Declarative catalog — Add catalog validation to the Gradle plugin

3 participants