Skip to content

Commit

Permalink
Add keyFields to Object struct
Browse files Browse the repository at this point in the history
  • Loading branch information
x-sheep committed Dec 5, 2024
1 parent 0feb11e commit e7cd57b
Show file tree
Hide file tree
Showing 12 changed files with 85 additions and 23 deletions.
4 changes: 3 additions & 1 deletion Tests/ApolloCodegenInternalTestHelpers/MockGraphQLType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ public extension GraphQLObjectType {
_ name: String = "",
interfaces: [GraphQLInterfaceType] = [],
fields: [String: GraphQLField] = [:],
keyFields: [String] = [],
documentation: String? = nil
) -> GraphQLObjectType {
GraphQLObjectType(
name: GraphQLName(schemaName: name),
documentation: documentation,
fields: fields,
interfaces: interfaces
interfaces: interfaces,
keyFields: keyFields
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ class ObjectTemplateTests: XCTestCase {
name: String = "Dog",
customName: String? = nil,
interfaces: [GraphQLInterfaceType] = [],
keyFields: [String] = [],
documentation: String? = nil,
config: ApolloCodegenConfiguration = .mock()
) {
let objectType = GraphQLObjectType.mock(
name,
interfaces: interfaces,
keyFields: keyFields,
documentation: documentation
)
objectType.name.customName = customName
Expand Down Expand Up @@ -82,7 +84,7 @@ class ObjectTemplateTests: XCTestCase {
implementedInterfaces: [
TestSchema.Interfaces.Animal.self,
TestSchema.Interfaces.Pet.self
]
],
"""

// when
Expand All @@ -106,7 +108,7 @@ class ObjectTemplateTests: XCTestCase {
implementedInterfaces: [
Interfaces.Animal.self,
Interfaces.Pet.self
]
],
"""

// when
Expand All @@ -121,7 +123,7 @@ class ObjectTemplateTests: XCTestCase {
buildSubject()

let expected = """
implementedInterfaces: []
implementedInterfaces: [],
"""

// when
Expand All @@ -130,6 +132,21 @@ class ObjectTemplateTests: XCTestCase {
// then
expect(actual).to(equalLineByLine(expected, atLine: 3, ignoringExtraLines: true))
}

func test_render_givenKeyFields_rendersKeyFieldArray() {
// given
buildSubject(keyFields: ["id"])

let expected = """
keyFields: ["id"]
"""

// when
let actual = renderSubject()

// then
expect(actual).to(equalLineByLine(expected, atLine: 4, ignoringExtraLines: true))
}

// MARK: Documentation Tests

Expand Down Expand Up @@ -213,7 +230,8 @@ class ObjectTemplateTests: XCTestCase {
// Renamed from GraphQL schema value: 'MyObject'
static let MyCustomObject = ApolloAPI.Object(
typename: "MyObject",
implementedInterfaces: [TestSchema.Interfaces.MyCustomInterface.self]
implementedInterfaces: [TestSchema.Interfaces.MyCustomInterface.self],
keyFields: nil
)
"""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,19 @@ struct ObjectTemplate: TemplateRenderer {
\(graphqlObject.name.typeNameDocumentation)
static let \(graphqlObject.render(as: .typename)) = \(config.ApolloAPITargetName).Object(
typename: "\(graphqlObject.name.schemaName)\",
implementedInterfaces: \(ImplementedInterfacesTemplate())
implementedInterfaces: \(ImplementedInterfacesTemplate()),
keyFields: \(KeyFieldsTemplate())
)
"""
}

private func KeyFieldsTemplate() -> TemplateString {
guard let fields = graphqlObject.keyFields, !fields.isEmpty else { return "nil" }

return """
[\(list: fields.map { "\"\($0)\"" })]
"""
}

private func ImplementedInterfacesTemplate() -> TemplateString {
return """
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -298,24 +298,19 @@ public final class CompilationResult: JavaScriptObjectDecodable {

public let parentType: GraphQLCompositeType

public let keyFields: [String]

public let selections: [Selection]

public init(
parentType: GraphQLCompositeType,
keyFields: [String] = [],
selections: [Selection] = []
) {
self.parentType = parentType
self.keyFields = keyFields
self.selections = selections
}

static func fromJSValue(_ jsValue: JSValue, bridge: isolated JavaScriptBridge) -> Self {
self.init(
parentType: .fromJSValue(jsValue["parentType"], bridge: bridge),
keyFields: jsValue["keyFields"],
selections: .fromJSValue(jsValue["selections"], bridge: bridge)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,16 +239,20 @@ public final class GraphQLObjectType: GraphQLCompositeType, GraphQLInterfaceImpl
public private(set) var fields: [String: GraphQLField]!

public private(set) var interfaces: [GraphQLInterfaceType]!

public private(set) var keyFields: [String]!

/// Initializer to be used for creating mock objects in tests only.
init(
name: GraphQLName,
documentation: String?,
fields: [String: GraphQLField],
interfaces: [GraphQLInterfaceType]
interfaces: [GraphQLInterfaceType],
keyFields: [String]
) {
self.fields = fields
self.interfaces = interfaces
self.keyFields = keyFields
super.init(name: name, documentation: documentation)
}

Expand All @@ -259,6 +263,7 @@ public final class GraphQLObjectType: GraphQLCompositeType, GraphQLInterfaceImpl
override func finalize(_ jsValue: JSValue, bridge: isolated JavaScriptBridge) {
self.fields = try! bridge.invokeMethod("getFields", on: jsValue)
self.interfaces = try! bridge.invokeMethod("getInterfaces", on: jsValue)
self.keyFields = jsValue["_apolloKeyFields"]
}

public override var debugDescription: String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import {
import * as ir from "./ir";
import { valueFromValueNode } from "./values";
import { ValidationOptions } from "../validationRules";
import { keyFieldsFor } from "../utilities/typePolicyResolver";

function filePathForNode(node: ASTNode): string | undefined {
return node.loc?.source?.name;
Expand Down Expand Up @@ -269,7 +268,6 @@ export function compileToIR(
): ir.SelectionSet {
return {
parentType,
keyFields: keyFieldsFor(parentType),
selections: selectionSetNode.selections
.map((selectionNode) =>
compileSelection(selectionNode, parentType, operationReferencedFragments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export interface FragmentDefinition {

export interface SelectionSet {
parentType: GraphQLCompositeType;
keyFields: string[];
selections: Selection[];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import { assertValidSchema, assertValidSDL } from "./utilities/graphql";
import {
addApolloCodegenSchemaExtensionToDocument,
} from "./utilities/apolloCodegenSchemaExtension";
import {
import {
addExperimentalDeferDirectiveToSDLDocument,
addExperimentalDeferDirectiveToIntrospectionSchema
} from "./utilities/experimentalDeferDirective";
import { addTypePolicyDirectivesToSchema } from "./utilities/typePolicyResolver";

// We need to export all the classes we want to map to native objects,
// so we have access to the constructor functions for type checks.
Expand Down Expand Up @@ -61,6 +62,7 @@ export function loadSchemaFromSources(sources: Source[]): GraphQLSchema {
assertValidSDL(document)

const schema = buildASTSchema(document, { assumeValid: true, assumeValidSDL: true })
addTypePolicyDirectivesToSchema(schema)
assertValidSchema(schema)

return schema
Expand All @@ -69,6 +71,7 @@ export function loadSchemaFromSources(sources: Source[]): GraphQLSchema {
var schema = loadSchemaFromIntrospectionResult(introspectionJSONResult.body)
document = addExperimentalDeferDirectiveToIntrospectionSchema(schema, document)
schema = extendSchema(schema, document, { assumeValid: true, assumeValidSDL: true })
addTypePolicyDirectivesToSchema(schema)

assertValidSchema(schema)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DirectiveNode, GraphQLCompositeType, GraphQLError, Kind } from "graphql";
import { DirectiveNode, GraphQLCompositeType, GraphQLError, GraphQLObjectType, GraphQLSchema, Kind } from "graphql";
import { directive_typePolicy } from "./apolloCodegenSchemaExtension";

const directiveName = directive_typePolicy.name.value;
Expand Down Expand Up @@ -53,3 +53,15 @@ export function keyFieldsFor(type: GraphQLCompositeType): string[] {

return fields.value.split(" ");
}

export function addTypePolicyDirectivesToSchema(schema: GraphQLSchema) {
const types = schema.getTypeMap();

for(const key in types) {
const type = types[key];

if(type instanceof GraphQLObjectType) {
(type as any)._apolloKeyFields = keyFieldsFor(type);
}
}
}
20 changes: 16 additions & 4 deletions apollo-ios/Sources/ApolloAPI/SchemaMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,22 @@ extension SchemaMetadata {
/// - Returns: A `String` representing the cache key for the `object` to be used by
/// `NormalizedCache` mechanisms.
@inlinable public static func cacheKey(for object: ObjectData) -> String? {
guard let type = graphQLType(for: object),
let info = configuration.cacheKeyInfo(for: type, object: object) else {
return nil
guard let type = graphQLType(for: object) else { return nil }

if let info = configuration.cacheKeyInfo(for: type, object: object) {
return "\(info.uniqueKeyGroup ?? type.typename):\(info.id)"
}

guard let keyFields = type.keyFields else { return nil }

let idValues = keyFields.compactMap {
if let v = object[$0] {
return try? String(_jsonValue: v._asAnyHashable)
}
return "null"
}
return "\(info.uniqueKeyGroup ?? type.typename):\(info.id)"

let id = idValues.joined(separator: "/")
return "\(type.typename):\(id)"
}
}
11 changes: 10 additions & 1 deletion apollo-ios/Sources/ApolloAPI/SchemaTypes/Object.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ public struct Object: Hashable, Sendable {
/// - Parameters:
/// - typename: The name of the type.
/// - implementedInterfaces: A list of the interfaces implemented by the type.
/// - keyFields: A list of field names that are used to uniquely identify an instance of this type.
public init(
typename: String,
implementedInterfaces: [Interface]
implementedInterfaces: [Interface],
keyFields: [String]? = nil
) {
self.typename = typename
self.implementedInterfaces = implementedInterfaces
if keyFields?.isEmpty == false {
self.keyFields = keyFields
} else {
self.keyFields = nil
}
}

/// A list of the interfaces implemented by the type.
Expand All @@ -26,6 +33,8 @@ public struct Object: Hashable, Sendable {
/// When an entity of the type is included in a GraphQL response its `__typename` field will
/// match this value.
public let typename: String

public let keyFields: [String]?

/// A helper function to determine if the receiver implements a given ``Interface`` Type.
///
Expand Down

0 comments on commit e7cd57b

Please sign in to comment.