Problem/Motivation

As I was working on various issues to make the config schema for field_storage_config be exposed via this wonderful module (see #3324769: Schema incorrect for config entity types with multi-part IDs (field_storage_config, field_config, entity_view_mode, entity_form_mode, entity_view_display and entity_form_display) + #3324824: Schema incorrect for config entity "fields" that are Maps and Sequences), specifically for https://www.drupal.org/project/field_ui_modern, I discovered/realized that there is a lot left to be explored WRT JSON Schema and specifically the validation aspects of it, which are covered by https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validati....

This module already exposes things like required, but rarely more than that. Ideally, we should be able to automatically transform both content entity validation constraints and config schema types + validation constraints (see #2164373: [META] Untie config validation from form validation — enables validatable Recipes, decoupled admin UIs …).

Relates issues but with a narrower scope: #3058852: Ensure valid "type" values for JSON data types, #3174991: Include all bundles in the enum for entity_reference fields and #3258702: Properly handle nullable values

P.S.: I thought https://modern-json-schema.com/json-schema-is-a-constraint-system was a particularly good high-level introduction.

Steps to reproduce

Proposed resolution

Generate a far richer schema, somewhat like this — note the use of:

  1. uniqueItems
  2. exclusiveMinimum
  3. required
  4. enum
  5. $drupalUiHint → Drupal-specific addition!
  6. $drupalPluginIdOfType → Drupal-specific addition!
{
  "$schema": "https://json-schema.org/draft/2019-09/hyper-schema",
  "$id": "/jsonapi/field_storage_config/field_storage_config/resource/schema",
  "title": "Field storage",
  "allOf": [
    {
      "type": "object",
      "properties": {
        "type": {
          "$ref": "#/definitions/type"
        },
        "attributes": {
          "$ref": "#/definitions/attributes"
        }
      }
    },
    {
      "$ref": "https://jsonapi.org/schema#/definitions/resource"
    }
  ],
  "definitions": {
    "type": {
      "const": "field_storage_config--field_storage_config"
    },
    "attributes": {
      "type": "object",
      "description": "Entity attributes",
      "required": [
        "entity_type",
        "field_name",
        "field_storage_config_type"
      ],
      "properties": {
        "uuid": {
          "type": "string",
          "title": "UUID",
          "format": "uuid",
          "maxLength": 128,
          "$drupalUiHint": "no-ui"
        },
        "drupal_internal__id": {
          "type": "string",
          "title": "drupal_internal__id",
          "pattern": "^(node|media|taxonomy_term|user)\\.[_a-z]+[_a-z0-9]*$",
          "$comment": "The ID consists of 2 parts: the entity type and the field name."
        },
        "field_name": {
          "type": "string",
          "title": "Field name",
          "pattern": "^[_a-z]+[_a-z0-9]*$",
          "description": "The name of the field."
        },
        "entity_type": {
          "type": "string",
          "title": "Entity Type",
          "enum": [
            "node",
            "media",
            "taxonomy_term",
            "user"
          ],
          "$drupalPluginIdOfType": "content_entity_type",
          "$drupalUiHint": "dropdown"
        },
        "field_storage_config_type": {
          "type": "string",
          "title": "Field type",
          "$comment": "Tricky: This is only not called 'type' because that is a reserved keyword in JSON:API.",
          "description": "Choose a field type.",
          "enum": [
            "uuid",
            "boolean",
            "decimal",
            "email",
            "entity_reference",
            "float",
            "integer",
            "string",
            "string_long",
            "timestamp",
            "comment",
            "datetime",
            "daterange"
          ],
          "$drupalPluginIdOfType": "field_type",
          "$drupalUiHint": "dropdown"
        },
        "langcode": {
          "type": "string",
          "title": "Language",
          "enum": ["en"],
          "default": "en",
          "$drupalUiHint": "no-ui"
        },
        "status": {
          "type": "boolean",
          "title": "Status",
          "default": true
        },
        "dependencies": {
          "type": "object",
          "title": "Dependencies",
          "$drupalUiHint": "no-ui",
          "additionalProperties": false,
          "properties": {
            "config": {
              "type": "array",
              "uniqueItems": true,
              "title": "Configuration dependencies",
              "items": {
                "type": "string",
                "title": "Configuration dependency"
              }
            },
            "content": {
              "type": "array",
              "uniqueItems": true,
              "title": "Content dependencies",
              "items": {
                "type": "string",
                "title": "Content dependency"
              }
            },
            "module": {
              "type": "array",
              "uniqueItems": true,
              "title": "Module dependencies",
              "items": {
                "type": "string",
                "title": "Module dependency"
              }
            },
            "theme": {
              "type": "array",
              "uniqueItems": true,
              "title": "Theme dependencies",
              "items": {
                "type": "string",
                "title": "Theme dependency"
              }
            }
          },
          "enforced": {
            "type": "object",
            "title": "Enforced dependencies",
            "additionalProperties": false,
            "properties": {
              "config": {
                "type": "array",
                "uniqueItems": true,
                "title": "Configuration dependencies",
                "items": {
                  "type": "string",
                  "title": "Configuration dependency"
                }
              },
              "content": {
                "type": "array",
                "uniqueItems": true,
                "title": "Content dependencies",
                "items": {
                  "type": "string",
                  "title": "Content dependency"
                }
              },
              "module": {
                "type": "array",
                "uniqueItems": true,
                "title": "Module dependencies",
                "items": {
                  "type": "string",
                  "title": "Module dependency"
                }
              },
              "theme": {
                "type": "array",
                "uniqueItems": true,
                "title": "Theme dependencies",
                "items": {
                  "type": "string",
                  "title": "Theme dependency"
                }
              }
            }
          }
        },
        "third_party_settings": {
          "type": "array",
          "items": {
            "properties": {
              "third_party_settings": {
                "title": "third_party_settings"
              }
            }
          }
        },
        "locked": {
          "type": "boolean",
          "title": "Locked",
          "description": "Whether the field storage is locked or not. When locked, field settings cannot be changed, no new fields can be created using this storage, and existing fields using this storage cannot be deleted.",
          "default": false,
          "$drupalUiHint": "no-ui-when-creating"
        },
        "cardinality": {
          "type": "integer",
          "title": "Cardinality",
          "description": "Allowed number of values",
          "exclusiveMinimum": 1,
          "default": 1
        },
        "translatable": {
          "type": "boolean",
          "title": "Translatable",
          "default": true
        },
        "indexes": {
          "type": "array",
          "$drupalUiHint": "no-ui",
          "items": {
            "properties": {
              "indexes": {
                "title": "indexes"
              }
            }
          }
        },
        "persist_with_no_fields": {
          "type": "boolean",
          "title": "Whether the field storage should be persisted when orphaned",
          "default": false,
          "$drupalUiHint": "no-ui"
        },
        "custom_storage": {
          "type": "boolean",
          "title": "custom_storage",
          "default": false,
          "$drupalUiHint": "no-ui"
        }
      },
      "additionalProperties": false
    }
  }
}

Remaining tasks

TBD

User interface changes

TBD

API changes

TBD

Data model changes

TBD

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new9.02 KB
wim leers’s picture

wim leers’s picture

Issue summary: View changes
m.stenta’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

We have automated tests now (#3257911: Add basic test coverage) so it would be good to include a test to demonstrate this issue and prevent regressions.