+6
CHANGELOG.md
+6
CHANGELOG.md
+1
-1
gleam.toml
+1
-1
gleam.toml
+9
-3
src/swell/executor.gleam
+9
-3
src/swell/executor.gleam
···
560
560
value.Object(_) -> {
561
561
// Check if field_type_def is a union type
562
562
// If so, resolve it to the concrete type first using the registry
563
-
let type_to_use = case
564
-
schema.is_union(field_type_def)
563
+
// Need to unwrap NonNull first since is_union only matches bare UnionType
564
+
let unwrapped_type = case
565
+
schema.inner_type(field_type_def)
566
+
{
567
+
option.Some(t) -> t
568
+
option.None -> field_type_def
569
+
}
570
+
let type_to_use = case schema.is_union(unwrapped_type)
565
571
{
566
572
True -> {
567
573
// Create context with the field value for type resolution
···
569
575
schema.context(option.Some(field_value))
570
576
case
571
577
schema.resolve_union_type_with_registry(
572
-
field_type_def,
578
+
unwrapped_type,
573
579
resolve_ctx,
574
580
type_registry,
575
581
)
+192
test/executor_test.gleam
+192
test/executor_test.gleam
···
1281
1281
_ -> should.fail()
1282
1282
}
1283
1283
}
1284
+
1285
+
// Test: Union type wrapped in NonNull resolves correctly
1286
+
// This tests the fix for fields like `node: NonNull(UnionType)` in connections
1287
+
// Previously, is_union check failed because it only matched bare UnionType
1288
+
pub fn execute_non_null_union_resolves_correctly_test() {
1289
+
// Create object types that will be part of the union
1290
+
let like_type =
1291
+
schema.object_type("Like", "A like record", [
1292
+
schema.field("uri", schema.string_type(), "Like URI", fn(ctx) {
1293
+
case ctx.data {
1294
+
option.Some(value.Object(fields)) -> {
1295
+
case list.key_find(fields, "uri") {
1296
+
Ok(uri_val) -> Ok(uri_val)
1297
+
Error(_) -> Ok(value.Null)
1298
+
}
1299
+
}
1300
+
_ -> Ok(value.Null)
1301
+
}
1302
+
}),
1303
+
])
1304
+
1305
+
let follow_type =
1306
+
schema.object_type("Follow", "A follow record", [
1307
+
schema.field("uri", schema.string_type(), "Follow URI", fn(ctx) {
1308
+
case ctx.data {
1309
+
option.Some(value.Object(fields)) -> {
1310
+
case list.key_find(fields, "uri") {
1311
+
Ok(uri_val) -> Ok(uri_val)
1312
+
Error(_) -> Ok(value.Null)
1313
+
}
1314
+
}
1315
+
_ -> Ok(value.Null)
1316
+
}
1317
+
}),
1318
+
])
1319
+
1320
+
// Type resolver that examines the "type" field
1321
+
let type_resolver = fn(ctx: schema.Context) -> Result(String, String) {
1322
+
case ctx.data {
1323
+
option.Some(value.Object(fields)) -> {
1324
+
case list.key_find(fields, "type") {
1325
+
Ok(value.String(type_name)) -> Ok(type_name)
1326
+
_ -> Error("No type field found")
1327
+
}
1328
+
}
1329
+
_ -> Error("No data")
1330
+
}
1331
+
}
1332
+
1333
+
// Create union type
1334
+
let notification_union =
1335
+
schema.union_type(
1336
+
"NotificationRecord",
1337
+
"A notification record",
1338
+
[like_type, follow_type],
1339
+
type_resolver,
1340
+
)
1341
+
1342
+
// Create edge type with node wrapped in NonNull - this is the key scenario
1343
+
let edge_type =
1344
+
schema.object_type("NotificationEdge", "An edge in the connection", [
1345
+
schema.field(
1346
+
"node",
1347
+
schema.non_null(notification_union),
1348
+
// NonNull wrapping union
1349
+
"The notification record",
1350
+
fn(ctx) {
1351
+
case ctx.data {
1352
+
option.Some(value.Object(fields)) -> {
1353
+
case list.key_find(fields, "node") {
1354
+
Ok(node_val) -> Ok(node_val)
1355
+
Error(_) -> Ok(value.Null)
1356
+
}
1357
+
}
1358
+
_ -> Ok(value.Null)
1359
+
}
1360
+
},
1361
+
),
1362
+
schema.field("cursor", schema.string_type(), "Cursor", fn(ctx) {
1363
+
case ctx.data {
1364
+
option.Some(value.Object(fields)) -> {
1365
+
case list.key_find(fields, "cursor") {
1366
+
Ok(cursor_val) -> Ok(cursor_val)
1367
+
Error(_) -> Ok(value.Null)
1368
+
}
1369
+
}
1370
+
_ -> Ok(value.Null)
1371
+
}
1372
+
}),
1373
+
])
1374
+
1375
+
// Create query type returning a list of edges
1376
+
let query_type =
1377
+
schema.object_type("Query", "Root query type", [
1378
+
schema.field("notifications", schema.list_type(edge_type), "Get notifications", fn(
1379
+
_ctx,
1380
+
) {
1381
+
Ok(
1382
+
value.List([
1383
+
value.Object([
1384
+
#(
1385
+
"node",
1386
+
value.Object([
1387
+
#("type", value.String("Like")),
1388
+
#("uri", value.String("at://user/like/1")),
1389
+
]),
1390
+
),
1391
+
#("cursor", value.String("cursor1")),
1392
+
]),
1393
+
value.Object([
1394
+
#(
1395
+
"node",
1396
+
value.Object([
1397
+
#("type", value.String("Follow")),
1398
+
#("uri", value.String("at://user/follow/1")),
1399
+
]),
1400
+
),
1401
+
#("cursor", value.String("cursor2")),
1402
+
]),
1403
+
]),
1404
+
)
1405
+
}),
1406
+
])
1407
+
1408
+
let test_schema = schema.schema(query_type, None)
1409
+
1410
+
// Query with inline fragments on the NonNull-wrapped union
1411
+
let query =
1412
+
"
1413
+
{
1414
+
notifications {
1415
+
cursor
1416
+
node {
1417
+
__typename
1418
+
... on Like {
1419
+
uri
1420
+
}
1421
+
... on Follow {
1422
+
uri
1423
+
}
1424
+
}
1425
+
}
1426
+
}
1427
+
"
1428
+
1429
+
let result = executor.execute(query, test_schema, schema.context(None))
1430
+
1431
+
case result {
1432
+
Ok(response) -> {
1433
+
case response.data {
1434
+
value.Object(fields) -> {
1435
+
case list.key_find(fields, "notifications") {
1436
+
Ok(value.List(edges)) -> {
1437
+
// Should have 2 edges
1438
+
list.length(edges) |> should.equal(2)
1439
+
1440
+
// First edge should be a Like with resolved fields
1441
+
case list.first(edges) {
1442
+
Ok(value.Object(edge_fields)) -> {
1443
+
case list.key_find(edge_fields, "node") {
1444
+
Ok(value.Object(node_fields)) -> {
1445
+
// __typename should be "Like" (resolved from union)
1446
+
case list.key_find(node_fields, "__typename") {
1447
+
Ok(value.String("Like")) -> should.be_true(True)
1448
+
Ok(value.String(other)) ->
1449
+
should.equal(other, "Like")
1450
+
_ -> should.fail()
1451
+
}
1452
+
// uri should be resolved from inline fragment
1453
+
case list.key_find(node_fields, "uri") {
1454
+
Ok(value.String("at://user/like/1")) ->
1455
+
should.be_true(True)
1456
+
_ -> should.fail()
1457
+
}
1458
+
}
1459
+
_ -> should.fail()
1460
+
}
1461
+
}
1462
+
_ -> should.fail()
1463
+
}
1464
+
}
1465
+
_ -> should.fail()
1466
+
}
1467
+
}
1468
+
_ -> should.fail()
1469
+
}
1470
+
}
1471
+
Error(err) -> {
1472
+
should.equal(err, "")
1473
+
}
1474
+
}
1475
+
}