Skip to content
Snippets Groups Projects
Commit f630b178 authored by Michael Stenta's avatar Michael Stenta
Browse files

Merge branch '3397275-getpossibleoptions' into '8.x-1.x'

Issue #3397275: Use OptionsProviderInterface::getPossibleOptions() for allowed...

See merge request !9
parents 6a8d1298 bc07436d
No related branches found
No related tags found
No related merge requests found
Pipeline #498110 passed
......@@ -24,6 +24,10 @@ services:
tags:
- { name: normalizer, priority: 20 }
# Data definition normalizers.
serializer.normalizer.data_definition.schema_json.boolean:
class: Drupal\jsonapi_schema\Normalizer\DataDefinitionBooleanNormalizer
tags:
- { name: normalizer, priority: 10 }
serializer.normalizer.data_definition.schema_json.string:
class: Drupal\jsonapi_schema\Normalizer\DataDefinitionStringNormalizer
tags:
......
......@@ -291,11 +291,14 @@ class JsonApiSchemaController extends ControllerBase {
$normalizer = $this->normalizer;
$entity_type = $this->entityTypeManager->getDefinition($resource_type->getEntityTypeId());
$bundle = $resource_type->getBundle();
$fields = array_reduce($resource_fields, function ($carry, ResourceTypeField $field) use ($normalizer, $entity_type, $bundle) {
$fields = array_reduce($resource_fields, function ($carry, ResourceTypeField $field) use ($normalizer, $resource_type, $entity_type, $bundle) {
$field_schema = $normalizer->normalize(
$this->staticDataDefinitionExtractor->extractField($entity_type, $bundle, $field->getInternalName()),
'schema_json',
['name' => $field->getPublicName()]
[
'name' => $field->getPublicName(),
'resource_type' => $resource_type,
]
);
$fields_member = $field instanceof ResourceTypeAttribute ? 'attributes' : 'relationships';
return NestedArray::mergeDeep($carry, [
......
<?php
namespace Drupal\jsonapi_schema\Normalizer;
/**
* Data definition normalizer.
*/
class DataDefinitionBooleanNormalizer extends DataDefinitionNormalizer {
/**
* {@inheritdoc}
*/
protected $supportedDataTypes = ['boolean'];
/**
* {@inheritdoc}
*/
protected function allowedValues(array $context = []) {
// Boolean fields implement OptionsProviderInterface, which would normally
// cause oneOf/anyOf allowed values to be included. The JSON Schema
// specification does not include these with boolean fields.
// @see https://json-schema.org/understanding-json-schema/reference/boolean
return [];
}
}
......@@ -5,6 +5,8 @@ namespace Drupal\jsonapi_schema\Normalizer;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\ListDataDefinitionInterface;
use Drupal\Core\TypedData\OptionsProviderInterface;
use Drupal\serialization\Normalizer\NormalizerBase;
/**
......@@ -61,16 +63,16 @@ class DataDefinitionNormalizer extends NormalizerBase {
$property = $this->extractPropertyData($entity, $context);
if (!is_object($property) && !empty($context['parent']) && $context['name'] == 'value') {
if ($maxLength = $context['parent']->getSetting('max_length')) {
/** @var \Drupal\Core\Field\TypedData\FieldItemDataDefinitionInterface $parent */
$parent = $context['parent'];
if ($maxLength = $parent->getSetting('max_length')) {
$property['maxLength'] = $maxLength;
}
if (empty($context['parent']->getSetting('allowed_values_function'))
&& !empty($context['parent']->getSetting('allowed_values'))
) {
$allowed_values = $context['parent']->getSetting('allowed_values');
// Include titles for UI integration.
// @see https://json-schema.org/understanding-json-schema/reference/generic.html?highlight=enum#annotations
// Get possible options for anyOf/oneOf.
$allowed_values = $this->allowedValues($context);
if (!empty($allowed_values)) {
$composition = $context['cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED ? 'anyOf' : 'oneOf';
array_walk($allowed_values, function (&$v, $k) {
$v = ['const' => $k, 'title' => $v];
......@@ -124,6 +126,61 @@ class DataDefinitionNormalizer extends NormalizerBase {
return $value;
}
/**
* Retrieve allowed values for oneOf/anyOf.
*
* @param array $context
* Serializer context.
*
* @return array
* Returns an associative array of allowed values.
*/
protected function allowedValues(array $context = []) {
// Only proceed if this is a field definition that implements
// ListDataDefinitionInterface. This is true of both base fields and config
// fields. We use ListDataDefinitionInterface::getItemDefinition() to get
// the field item definition.
if (empty($context['parent']) || !($context['parent']->getFieldDefinition() instanceof ListDataDefinitionInterface)) {
return [];
}
/** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
$field_definition = $context['parent']->getFieldDefinition();
/** @var \Drupal\Core\Field\TypedData\FieldItemDataDefinitionInterface $item_definition */
$item_definition = $context['parent']->getFieldDefinition()->getItemDefinition();
// Only proceed if the base item definition class is a subclass of
// OptionsProviderInterface, which provides a getPossibleOptions() method.
if (!is_subclass_of($item_definition->getClass(), OptionsProviderInterface::class)) {
return [];
}
// We require the resource type context information that is passed in from
// JsonApiSchemaController in order to get the entity type and bundle of
// the resource being serialized. We use this to generate a mock entity that
// is passed into the allowed_values_function, which can refine the allowed
// values accordingly.
if (empty($context['resource_type'])) {
return [];
}
/** @var \Drupal\jsonapi\ResourceType\ResourceType $resource_type */
$resource_type = $context['resource_type'];
// Get the entity type and bundle.
$entity_type = $resource_type->getEntityTypeId();
$bundle = $resource_type->getBundle();
// Mock an entity of this type and bundle.
$entity_type_manager = \Drupal::entityTypeManager();
$entity_type_definition = $entity_type_manager->getDefinition($entity_type);
$bundle_key = $entity_type_definition->getKey('bundle');
/** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
$entity = $entity_type_manager->getStorage($entity_type)->create([$bundle_key => $bundle]);
// Return allowed options for the field.
return $field_definition->getFieldStorageDefinition()->getOptionsProvider($field_definition->getName(), $entity)->getPossibleOptions();
}
/**
* Normalize an array of data definitions.
*
......
name: JSON:API Schema Test
type: module
description: Support module for JSON:API Schema testing.
package: Testing
core_version_requirement: ^8.8 || ^9 || ^10
<?php
/**
* @file
* Contains jsonapi_schema_test.module.
*/
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Implements hook_entity_base_field_info().
*/
function jsonapi_schema_test_entity_base_field_info(EntityTypeInterface $entity_type) {
$fields = [];
if ($entity_type->id() == 'node') {
// Add a list_string base field with an allowed values function.
$field = BaseFieldDefinition::create('list_string');
$field->setLabel('Test allowed values function');
$field->setDescription('Test allowed values function description');
$field->setCardinality(-1);
$field->setSetting('allowed_values', [
'value1' => 'Value 1',
'value2' => 'Value 2',
'value3' => 'Value 3',
]);
$fields['test_allowed_values_function'] = $field;
}
return $fields;
}
/**
* Test allowed_values_function.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
* The field definition.
* @param \Drupal\Core\Entity\ContentEntityInterface|null $entity
* The entity being created if applicable.
* @param bool $cacheable
* Boolean indicating if the allowed values can be cached. Defaults to TRUE.
*
* @return array
* Returns an array of allowed values.
*/
function jsonapi_schema_test_allowed_values_function(FieldStorageDefinitionInterface $definition, ?ContentEntityInterface $entity = NULL, bool &$cacheable = TRUE) {
return [
'value1' => 'Value 1',
'value2' => 'Value 2',
'value3' => 'Value3',
];
}
......@@ -217,7 +217,9 @@ class JsonApiSchemaTest extends KernelTestBase {
'file',
'jsonapi',
'jsonapi_schema',
'jsonapi_schema_test',
'node',
'options',
'serialization',
'system',
'user',
......@@ -405,6 +407,29 @@ class JsonApiSchemaTest extends KernelTestBase {
]);
$field->save();
// Add a list_string field with allowed values.
$field_storage = FieldStorageConfig::create([
'field_name' => 'test_allowed_values',
'type' => 'list_string',
'entity_type' => 'node',
'settings' => [
'allowed_values' => [
'option1' => 'Option 1',
'option2' => 'Option 2',
'option3' => 'Option 3',
],
],
]);
$field_storage->save();
$field = FieldConfig::create([
'field_name' => 'test_allowed_values',
'entity_type' => 'node',
'bundle' => 'article',
'label' => 'Test allowed values',
'description' => 'Test allowed values description',
]);
$field->save();
// Add a URI field.
$field_storage = FieldStorageConfig::create([
'field_name' => 'test_uri',
......@@ -587,6 +612,11 @@ class JsonApiSchemaTest extends KernelTestBase {
'type' => 'string',
'title' => 'Language',
'cardinality' => 1,
'allowed_values' => [
'en' => 'English',
'und' => 'Not specified',
'zxx' => 'Not applicable',
],
],
'revision_timestamp' => [
'type' => 'string',
......@@ -686,6 +716,28 @@ class JsonApiSchemaTest extends KernelTestBase {
'description' => 'Test string description',
'cardinality' => -1,
],
'test_allowed_values' => [
'type' => 'string',
'title' => 'Test allowed values',
'description' => 'Test allowed values description',
'cardinality' => 1,
'allowed_values' => [
'option1' => 'Option 1',
'option2' => 'Option 2',
'option3' => 'Option 3',
],
],
'test_allowed_values_function' => [
'type' => 'string',
'title' => 'Test allowed values function',
'description' => 'Test allowed values function description',
'cardinality' => -1,
'allowed_values' => [
'value1' => 'Value 1',
'value2' => 'Value 2',
'value3' => 'Value 3',
],
],
'test_uri' => [
'type' => 'string',
'title' => 'Test URI',
......@@ -712,6 +764,21 @@ class JsonApiSchemaTest extends KernelTestBase {
else {
$this->assertArrayNotHasKey('description', $data['definitions']['attributes']['properties'][$name]);
}
// Test allowed values.
$property = $data['definitions']['attributes']['properties'][$name];
$property = $attribute['cardinality'] === 1 ? $property : $property['items'];
$composition = $attribute['cardinality'] === 1 ? 'oneOf' : 'anyOf';
if (isset($attribute['allowed_values'])) {
foreach (array_keys($attribute['allowed_values']) as $i => $const) {
$this->assertEquals($const, $property[$composition][$i]['const']);
$this->assertEquals($attribute['allowed_values'][$const], $property[$composition][$i]['title']);
}
}
else {
$this->assertArrayNotHasKey('oneOf', $property);
$this->assertArrayNotHasKey('anyOf', $property);
}
}
// Test that required attributes are declared.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment