Skip to content

Commit

Permalink
Merge pull request #1474 from apollographql/custom-scalar-playground-…
Browse files Browse the repository at this point in the history
…page

Add minor docs + Custom Scalar playground page
  • Loading branch information
designatednerd authored Oct 29, 2020
2 parents 4b4084e + ea7c11d commit e0f65fe
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 8 deletions.
2 changes: 1 addition & 1 deletion Apollo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2012,7 +2012,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1130;
LastUpgradeCheck = 1200;
LastUpgradeCheck = 1210;
ORGANIZATIONNAME = "Apollo GraphQL";
TargetAttributes = {
9B2DFBB524E1FA0D00ED3AE6 = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1210"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
2 changes: 1 addition & 1 deletion Apollo.xcodeproj/xcshareddata/xcschemes/Apollo.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1210"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1210"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1210"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1210"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1210"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//: [SQLite Cache](@previous)

import Foundation
import Apollo

/*:
## Custom Scalars
Custom scalars allow you to define your own scalar types based on existing GraphQL scalar types. You can then use typealiasing and custom initializers to make these work with Swift types.
To use these types in your code, you'll need to first make sure that when you generate code, `passthroughCustomScalars` is true, since otherwise you'll only get the underlying scalar types.
The most common example is having your own date type. In this example, you'll take a type generated using `passthroughCustomScalars` called `CustomDate` and transform it into a `Foundation.Date` object you can use throughout your code.
After generating the code using our code generation tooling, in a file **other than your API.swift file**, you will need to add a typealias to the underlying Swift type, so that Swift knows `CustomDate` is actually just a `Date` under the hood:
*/

public typealias CustomDate = Foundation.Date

/*:
Next, add an extension that conforms that type to the `JSONDecodable` protocol, which takes in a raw value from JSON and then tries to create your underlying type:
*/

extension CustomDate: JSONDecodable {

public init(jsonValue value: JSONValue) throws {
// Since you know the underlying scalar type of `CustomDate` is a String, you can pull the original string out.
guard let string = value as? String else {
throw JSONDecodingError.couldNotConvert(value: value, to: String.self)
}

// Next, if that worked, you parse the date using whatever formatter you want. In this case, since we know we're expecting an ISO8601 format, we can use the ISO8601 formatter.
guard let date = ISO8601DateFormatter().date(from: string) else {
throw JSONDecodingError.couldNotConvert(value: value, to: Date.self)
}

// Now that we've got a `Foundation.Date`, we can assign it here as `self` since `CustomDate` is typealiased to `Foundation.Date`.
self = date
}
}

/*:
Again, for emphasis: **Do not** put these bits into a generated file, because they will get overwritten when you regenerate code.
*/

/*:
## A sample schema and query
Here's a small sample schema we'll use to simulate the code generated here by our codegen tool:
```graphql
type DateInfo {
date: CustomDate!
}
type Query {
whatTimeIsIt: DateInfo!
}
```
Along with a small query to that schema:
```graphql
query CustomScalarDate {
whatTimeIsIt {
__typename
date
}
}
```
Below is the code that would be generated by this query against that schema (you can close it up by flipping the flippy triangle on line 77 if you don't want to look at it):
*/

public final class CustomScalarDateQuery: GraphQLQuery {
/// The raw GraphQL definition of this operation.
public let operationDefinition: String =
"""
query CustomScalarDate {
whatTimeIsIt {
__typename
date
}
}
"""

public let operationName: String = "CustomScalarDate"

public let operationIdentifier: String? = "(not relevant here)"

public init() {
}

public var variables: GraphQLMap? {
return nil
}

public struct Data: GraphQLSelectionSet {
public static let possibleTypes: [String] = ["Query"]

public static var selections: [GraphQLSelection] {
return [
GraphQLField("whatTimeIsIt", arguments: nil, type: .object(WhatTimeIsIt.selections)),
]
}

public private(set) var resultMap: ResultMap

public init(unsafeResultMap: ResultMap) {
self.resultMap = unsafeResultMap
}

public init(whatTimeIsIt: WhatTimeIsIt? = nil) {
self.init(unsafeResultMap: ["__typename": "Query", "whatTimeIsIt": whatTimeIsIt.flatMap { (value: WhatTimeIsIt) -> ResultMap in value.resultMap }])
}

public var whatTimeIsIt: WhatTimeIsIt? {
get {
return (resultMap["whatTimeIsIt"] as? ResultMap).flatMap { WhatTimeIsIt(unsafeResultMap: $0) }
}
set {
resultMap.updateValue(newValue?.resultMap, forKey: "whatTimeIsIt")
}
}

public struct WhatTimeIsIt: GraphQLSelectionSet {
public static var selections: [GraphQLSelection] {
return [
GraphQLField("__typename", type: .nonNull(.scalar(String.self))),
GraphQLField("date", type: .nonNull(.scalar(CustomDate.self))),
]
}

public private(set) var resultMap: ResultMap

public init(unsafeResultMap: ResultMap) {
self.resultMap = unsafeResultMap
}

public var __typename: String {
get {
return resultMap["__typename"]! as! String
}
set {
resultMap.updateValue(newValue, forKey: "__typename")
}
}

public var date: CustomDate {
get {
return resultMap["date"]! as! CustomDate
}
set {
resultMap.updateValue(newValue, forKey: "date")
}
}
}
}
}

/*:
Next, we'll create the raw JSON that you'd receive in return, with the `CustomDate` type being a string that has an ISO8601-formatted date.
*/

let json =
"""
{
"data": {
"whatTimeIsIt": {
"__typename": "DateInfo",
"date": "2020-03-17T21:12:34Z"
}
}
}
"""

/*:
We'll turn this into the JSON that gets used by `GraphQLResponse`:
*/
let data = json.data(using: .utf8)!

let toJSON = try! JSONSerializationFormat.deserialize(data: data)
let asObject = toJSON as! JSONObject

let response: GraphQLResponse<CustomScalarDateQuery.Data> = GraphQLResponse(operation: CustomScalarDateQuery(), body: asObject)

/*:
Next, we'll turn that response into a result and try to get the date that was parsed:
*/
let result = try! response.parseResultFast()

guard let parsedDate = result.data?.whatTimeIsIt?.date else {
fatalError("date did not parse correctly!")
}

/*:
Finally, we'll use a date formatter set to UTC using the en-US locale, with short time style and long date style to print out the date. This should print out "March 17, 2020 at 9:12 PM".
*/

let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en-US")
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")!

print(dateFormatter.string(from: parsedDate))

//: [Next](@next)
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@ PlaygroundPage.current.needsIndefiniteExecution = true
//: Once "FINISHED" prints, you can open the database file at the path printed out with "File path" and examine it to see the persisted data.
//: If you don't already have a SQLite file browser, you can try the free one at https://sqlitebrowser.org/

//: [Next](@next)
//: [Custom Scalars](@next)
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
<page name='Queries'/>
<page name='Mutations'/>
<page name='Subscriptions'/>
<page name='SQLiteCache'/>
<page name='CustomScalars'/>
</pages>
</playground>
8 changes: 8 additions & 0 deletions docs/source/fetching-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ apollo.fetch(query: HeroAndFriendsNamesQuery(episode: .empire)) { result in

Because the above query won't fetch `appearsIn`, this property is not part of the returned result type and cannot be accessed here.

### Notes on working with Custom Scalars

Custom scalars are types defined by your schema that are based on other GraphQL scalar types (such as `String` or `Int`). Without intervention, code generation will use the underlying types to generate code for the custom scalars.

If you want to use the custom scalars within your code, you must set `passthroughCustomScalars` to true either at the command line or using Swift Scripting.

Once you've done that, you can either create your own type locally or use a `typealias` to declare an equivilent. This is very, very frequently used with `Date` types. Please see the [Custom Scalar Playground Page](https://github.com/apollographql/apollo-ios/main/custom-scalar-playground-page/Playgrounds/ApolloMacPlayground.playground/Pages/CustomScalars.xcplaygroundpage) available within the `apollo-iOS` repo for a full example using a custom date type.

## Specifying a cache policy

[This section has moved to the Caching documentation](/caching/).
Expand Down

0 comments on commit e0f65fe

Please sign in to comment.