Skip to content

Commit 6d175b4

Browse files
committed
GQL: Handle unknown values
Since we cannot know or define the structure of unknown values, we return their contents in a single "content" field containing arbitrary JSON. Defining a custom JSON scalar type is common practice in GraphQL for such cases. Bug: T404839 Change-Id: I21d660f209a0a6aa25febf80f4f0dbdf5ac85ddc
1 parent 1078673 commit 6d175b4

File tree

5 files changed

+99
-7
lines changed

5 files changed

+99
-7
lines changed

lib/includes/DataTypeDefinitions.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,7 @@ public function getNormalizerFactoryCallbacks(): array {
413413
* @return callable[] List of callbacks by data type, without "PT:" prefixes.
414414
*/
415415
public function getGraphqlValueTypes(): array {
416-
return $this->resolveValueTypeFallback(
417-
$this->getMapForDefinitionField( 'graphql-value-type' ),
418-
allowMissing: true, // TODO remove or set to false once T404692 is done since it should no longer be needed
419-
);
416+
return $this->resolveValueTypeFallback( $this->getMapForDefinitionField( 'graphql-value-type' ) );
420417
}
421418

422419
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php declare( strict_types=1 );
2+
3+
namespace Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\Schema;
4+
5+
use GraphQL\Language\AST\Node;
6+
use GraphQL\Type\Definition\ScalarType;
7+
8+
/**
9+
* @license GPL-2.0-or-later
10+
*/
11+
class JsonType extends ScalarType {
12+
public function serialize( mixed $value ): mixed {
13+
return $value;
14+
}
15+
16+
public function parseValue( mixed $value ): mixed {
17+
return $value;
18+
}
19+
20+
public function parseLiteral( Node $valueNode, ?array $variables = null ): mixed {
21+
return $valueNode->jsonSerialize();
22+
}
23+
}

repo/domains/reuse/src/Infrastructure/GraphQL/Schema/ValueType.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\Schema;
44

5+
use GraphQL\Type\Definition\ObjectType;
6+
use GraphQL\Type\Definition\Type;
57
use GraphQL\Type\Definition\UnionType;
68
use Wikibase\Repo\Domains\Reuse\Domain\Model\PropertyValuePair;
79
use Wikibase\Repo\Domains\Reuse\Domain\Model\Statement;
@@ -13,10 +15,20 @@ class ValueType extends UnionType {
1315

1416
public function __construct( array $valueTypeCallbacks ) {
1517
$valueTypes = array_map( fn( $c ) => $c(), $valueTypeCallbacks );
18+
$unknownValueType = new ObjectType( [
19+
'name' => 'UnknownValue',
20+
'fields' => [
21+
'content' => [
22+
'type' => Type::nonNull( new JsonType() ),
23+
'resolve' => fn( Statement|PropertyValuePair $valueProvider ) => $valueProvider->value->content->getArrayValue(),
24+
],
25+
],
26+
] );
1627

1728
parent::__construct( [
18-
'types' => array_values( array_unique( $valueTypes ) ),
19-
'resolveType' => fn( Statement|PropertyValuePair $valueProvider ) => $valueTypes[$valueProvider->property->dataType],
29+
'types' => array_values( [ ...array_unique( $valueTypes ), $unknownValueType ] ),
30+
'resolveType' => fn( Statement|PropertyValuePair $valueProvider ) => $valueTypes[$valueProvider->property->dataType]
31+
?? $unknownValueType,
2032
] );
2133
}
2234

repo/domains/reuse/src/Infrastructure/GraphQL/schema.graphql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ type PredicateProperty {
5252
label(languageCode: LanguageCode!): String
5353
}
5454

55-
union Value = StringValue | ItemValue | PropertyValue | GlobeCoordinateValue | MonolingualTextValue | QuantityValue | TimeValue | EntityValue
55+
union Value = StringValue | ItemValue | PropertyValue | GlobeCoordinateValue | MonolingualTextValue | QuantityValue | TimeValue | EntityValue | UnknownValue
5656

5757
type StringValue {
5858
content: String!
@@ -100,6 +100,12 @@ type EntityValue {
100100
id: String!
101101
}
102102

103+
type UnknownValue {
104+
content: Json!
105+
}
106+
107+
scalar Json
108+
103109
enum ValueType {
104110
novalue
105111
somevalue

repo/domains/reuse/tests/phpunit/Infrastructure/GraphQL/GraphQLServiceTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use DataValues\StringValue;
1111
use DataValues\TimeValue;
1212
use DataValues\UnboundedQuantityValue;
13+
use DataValues\UnDeserializableValue;
1314
use Generator;
1415
use GraphQL\GraphQL;
1516
use MediaWiki\Site\HashSiteStore;
@@ -62,6 +63,7 @@ class GraphQLServiceTest extends MediaWikiIntegrationTestCase {
6263
private static Property $propertyUsedAsValue;
6364
private static Property $qualifierProperty;
6465
private static Property $customEntityIdProperty;
66+
private static Property $unknownTypeProperty;
6567
private static MediaWikiSite $sitelinkSite;
6668
private const ALLOWED_SITELINK_SITES = [ 'examplewiki', 'otherwiki' ];
6769
private const CUSTOM_ENTITY_DATA_TYPE = 'test-type';
@@ -103,6 +105,7 @@ public function testQuery( string $query, array $expectedResult ): void {
103105
self::$timeProperty,
104106
self::$propertyTypeProperty,
105107
self::$customEntityIdProperty,
108+
self::$unknownTypeProperty,
106109
] as $property ) {
107110
$dataTypeLookup->setDataTypeForProperty( $property->getId(), $property->getDataTypeId() );
108111
}
@@ -153,6 +156,7 @@ public function queryProvider(): Generator {
153156
$statementWithCustomEntityIdValuePropertyId = 'P13';
154157
$statementWithItemValueQualifierPropertyId = $statementWithItemValuePropertyId; // also type wikibase-item so we can just reuse it.
155158
$statementReferencePropertyId = 'P11';
159+
$unknownTypePropertyId = 'P12';
156160
$unusedPropertyId = 'P9999';
157161
$qualifierStringValue = 'qualifierStringValue';
158162
$statementStringValue = 'statementStringValue';
@@ -227,6 +231,22 @@ public function queryProvider(): Generator {
227231
->withValue( new EntityIdValue( self::$propertyUsedAsValue->getId() ) )
228232
->build();
229233

234+
self::$unknownTypeProperty = new Property( new NumericPropertyId( $unknownTypePropertyId ), null, 'unknown-type' );
235+
$unknownValueData = [ 'some' => 'data' ];
236+
$statementWithUnknownType = NewStatement::forProperty( $unknownTypePropertyId )
237+
->withSubject( $itemId )
238+
->withSomeGuid()
239+
// Ideally we would just stub DataValue here, but that's not possible because it extends Serializable,
240+
// which is deprecated and emits a warning.
241+
->withValue( new UnDeserializableValue( $unknownValueData, null, 'test value' ) );
242+
243+
$deletedProperty = 'P999';
244+
$valueUsedInStatementWithDeletedProperty = new StringValue( 'deleted value' );
245+
$statementWithDeletedProperty = NewStatement::forProperty( $deletedProperty )
246+
->withSubject( $itemId )
247+
->withSomeGuid()
248+
->withValue( $valueUsedInStatementWithDeletedProperty );
249+
230250
$statementWithNoValue = NewStatement::noValueFor( ( $statementWithNoValuePropertyId ) )
231251
->withSubject( $itemId )
232252
->withSomeGuid()
@@ -309,6 +329,8 @@ public function queryProvider(): Generator {
309329
->andStatement( $statementWithNoValue )
310330
->andStatement( $statementWithSomeValue )
311331
->andStatement( $statementWithCustomEntityIdValue )
332+
->andStatement( $statementWithUnknownType )
333+
->andStatement( $statementWithDeletedProperty )
312334
->build();
313335

314336
$item2Id = 'Q321';
@@ -655,6 +677,38 @@ public function queryProvider(): Generator {
655677
],
656678
],
657679
];
680+
yield 'statement with unknown value type' => [
681+
"{ item(id: \"$itemId\") {
682+
statements(propertyId: \"$unknownTypePropertyId\") {
683+
value { ...on UnknownValue { content } }
684+
}
685+
} }",
686+
[
687+
'data' => [
688+
'item' => [
689+
'statements' => [
690+
[ 'value' => [ 'content' => $unknownValueData ] ],
691+
],
692+
],
693+
],
694+
],
695+
];
696+
yield 'statement with deleted property' => [
697+
"{ item(id: \"$itemId\") {
698+
statements(propertyId: \"$deletedProperty\") {
699+
value { ...on UnknownValue { content } }
700+
}
701+
} }",
702+
[
703+
'data' => [
704+
'item' => [
705+
'statements' => [
706+
[ 'value' => [ 'content' => $valueUsedInStatementWithDeletedProperty->getArrayValue() ] ],
707+
],
708+
],
709+
],
710+
],
711+
];
658712
yield 'labels of predicate properties' => [
659713
"{ item(id: \"$itemId\") {
660714
statements(propertyId: \"{$statementWithStringValuePropertyId}\") {

0 commit comments

Comments
 (0)