Skip to content

Commit 031fefd

Browse files
feat: Allows mutating scalar parse and serialization
This enables `buildSchemaAST` to create Scalar types with default values, and then for a client to override them, similar to what is done for GraphQLField resolver/subscribe functions.
1 parent 28257ca commit 031fefd

1 file changed

Lines changed: 30 additions & 7 deletions

File tree

‎Sources/GraphQL/Type/Definition.swift‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,17 +135,34 @@ extension GraphQLNonNull: GraphQLWrapperType {}
135135
/// try $0.map.asBool(converting: true)
136136
/// }
137137
/// )
138-
public final class GraphQLScalarType: Sendable {
138+
public final class GraphQLScalarType: @unchecked Sendable {
139139
public let name: String
140140
public let description: String?
141141
public let specifiedByURL: String?
142142
public let astNode: ScalarTypeDefinition?
143143
public let extensionASTNodes: [ScalarExtensionDefinition]
144144
public let kind: TypeKind = .scalar
145145

146-
let serialize: @Sendable (Any) throws -> Map
147-
let parseValue: @Sendable (Map) throws -> Map
148-
let parseLiteral: @Sendable (Value) throws -> Map
146+
// Callbacks may be configured after building an SDL schema, before it is used for execution.
147+
// Synchronize access so reading a callback cannot race with its replacement.
148+
private var _serialize: @Sendable (Any) throws -> Map
149+
private var _parseValue: @Sendable (Map) throws -> Map
150+
private var _parseLiteral: @Sendable (Value) throws -> Map
151+
152+
public var serialize: @Sendable (Any) throws -> Map {
153+
get { scalarPropertyQueue.sync { _serialize } }
154+
set { scalarPropertyQueue.sync(flags: .barrier) { _serialize = newValue } }
155+
}
156+
157+
public var parseValue: @Sendable (Map) throws -> Map {
158+
get { scalarPropertyQueue.sync { _parseValue } }
159+
set { scalarPropertyQueue.sync(flags: .barrier) { _parseValue = newValue } }
160+
}
161+
162+
public var parseLiteral: @Sendable (Value) throws -> Map {
163+
get { scalarPropertyQueue.sync { _parseLiteral } }
164+
set { scalarPropertyQueue.sync(flags: .barrier) { _parseLiteral = newValue } }
165+
}
149166

150167
public init(
151168
name: String,
@@ -163,9 +180,9 @@ public final class GraphQLScalarType: Sendable {
163180
self.specifiedByURL = specifiedByURL
164181
self.astNode = astNode
165182
self.extensionASTNodes = extensionASTNodes
166-
self.serialize = serialize
167-
self.parseValue = parseValue ?? defaultParseValue
168-
self.parseLiteral = parseLiteral ?? defaultParseLiteral
183+
_serialize = serialize
184+
_parseValue = parseValue ?? defaultParseValue
185+
_parseLiteral = parseLiteral ?? defaultParseLiteral
169186
}
170187

171188
/// Serializes an internal value to include in a response.
@@ -1421,3 +1438,9 @@ private let fieldPropertyQueue = DispatchQueue(
14211438
label: "graphql.field.properties",
14221439
attributes: .concurrent
14231440
)
1441+
1442+
/// Shared reader/writer queue for scalar callbacks.
1443+
private let scalarPropertyQueue = DispatchQueue(
1444+
label: "graphql.scalar.properties",
1445+
attributes: .concurrent
1446+
)

0 commit comments

Comments
 (0)