SwiftyGraphQL 2.1.0

SwiftyGraphQL 2.1.0

hiimtmac 维护。



  • hiimtmac

SwiftyGraphQL

Platform Swift Version

这个库帮助编写更安全(类型安全)的 GraphQL 查询和变更,对于那些害怕生成代码的库的人来说。

要求

  • Swift 5.3+
  • iOS 11+, macOS 14+

安装

Swift Package Manager

创建一个 Package.swift 文件。

import PackageDescription

let package = Package(
  name: "TestProject",
  dependencies: [
    .package(url: "https://github.com/hiimtmac/SwiftyGraphQL.git", from: "2.0.0")
  ]
)

Cocoapods

target 'MyApp' do
  pod 'SwiftyGraphQL', '~> 2.0'
end

使用方法

一般使用方法如下

let query = GQL(name: "HeroNameAndFriends") {
    GQLNode("hero") {
        "name"
        "age"
        GQLNode("friends") {
            "name"
            "age"
        }
    }
    .withArgument("episode", variableName: "episode")
}
.withVariable("episode", value: "JEDI")
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    age
    friends {
      name
      age
    }
  }
}

GQLNode

let node = GQLNode("Root", alias: "root") {
    GQLNode("sub") {
        "thing1"
        "thing2"
    }
    "thing3"
    "thing4"
    GQLFragment(Object.self)
}
{
    root: Root {
        sub {
            thing1
            thing2
        }
        thing3
        thing4
        ...object
    }
}

fragment object on Object {
    ... // object attributes
}

注意:输出将使用空格而不是换行符分隔

{ root: Root { sub { thing1 thing2 } thing3 thing4 ...object } } fragment object on Object { ... // object attributes }

别名

.init(_ name: String, alias: String? = nil)中添加节点别名。别名将改变返回json键对此节点的更改。

let node = GQLNode("sub") {
    "thing1"
    "thing2"
}
sub { thing1 thing2 }
let node = GQLNode("sub", alias: "cool") {
    "thing1"
    "thing2"
}
cool: sub { thing1 thing2 }

参数

待添加文档

变量

待添加文档

指令

当前支持以下指令

  • 跳过指令
  • 包含指令

片段

协议GQLFragmentable可以使对象轻松地编码到查询或请求中。

struct Object {
    let name: String
    let age: Int
}

extension Object: GQLFragmentable {
    // required
    static var graqhQl: GraphQLExpression {
        "name"
        "age"
    }
    // optional - will be synthesized from object Type name if not implemented
    static let fragmentName = "myobject"
    static var fragmentType = "MyObject"
}
...myobject
fragment myobject on MyObject { name age }

提示:如果您的对象具有自定义 CodingKeys 实现,请将您的类型符合 GQLCodable 以及其 CodingKeys 符合 CaseIterable

struct Object: GQLFragmentable, GQLCodable {
    let name: String
    let age: Int

    enum CodingKeys: String, CodingKey, CaseIterable {
        case name
        case age
    }
}
...object
fragment object on Object { name age }

一旦您的对象符合 GQLFragmentable,它就可以在查询中的 GQLFragment 对象中使用

let node = GQLNode("test") {
    GQLFragment(Object.self)
    Object.asFragment()
}
test { ...myobject }
fragment myobject on MyObject { name age }

查询

GQL 函数构建器对象用于创建 graphQL 查询(如上所述)。

示例

let query = GQL {
    GQLNode("allNodes") {
        GQLNode("frag") {
            Frag2.asFragment()
        }
        "hi"
        "ok"
    }
}

let json = try query.encode()
query {
    allNodes {
        frag {
            ...frag2
        }
        hi
        ok
    }
}

fragment frag2 on Frag2 {
    ...
}
*/
{
  "query": "{ query allNodes { frag { ...frag2 } hi ok } } fragment frag2 on Frag2 { ... }"
}

突变

GQLMutationGQLQuery 类似。通常,您会包含用于突变的 operationName。

let mutation = GQL(.mutation, name: "NewPerson") {
    GQLNode("newPerson") {
        GQLNode("person", alias: "createdPerson") {
            GQLFragment(Frag2.self)
        }
    }
    .withArgument("id", value: "123")
    .withArgument("name", value: "taylor")
    .withArgument("age", value: 666)
}

let json = try query.encode()
mutation NewPerson {
    newPerson(id: "123", name: "taylor", age: 666) {
        createdPerson: person {
            ...frag2
        }
    }
}

fragment frag2 on Frag2 {
    ...
}
{
  "query": "mutation { newPerson(id: \"123\", name: \"taylor\", age: 666) { createdPerson: person { ...frag2 } } fragment frag2 on Frag2 { ... }"
}

变量

GQL 添加变量。这将包括将参数包含在查询的头部,并在您将查询转换为 json 时将其内容嵌入到字典中。

let query = GQL(name: "GetIt") {
    GQLNode("node") {
        "hello"
    }
    .withArgument("rating", variableName: "r")
}
.withVariable("rating", value: 5)

let json = try query.encode()
mutation GetIt($rating: Int) {
    node(rating: $r) {
        "hello"
    }
}
{
  "query": "mutation GetIt($rating: Int) { node(rating: $r) { hello } }",
  "variables": {
    "rating": 5
  }
}

响应

已包括响应解码辅助程序。这不需要所有的返回结构都包含在对象上方的_levels_ key。

extension JSONDecoder {
    public func graphQLDecode<T>(_ type: T.Type, from data: Data) throws -> T where T: Decodable {
        do {
            return try decode(type, from: data)
        } catch {
            let graphQLError = try? JSONDecoder().decode(GQLErrors.self, from: data)
            throw graphQLError ?? error
        }
    }
}

可以解码类似以下格式的响应

{
    "data": {
        {
            "name": "taylor",
            "age": 666
        }
    }
}

另外,如果解码无法完成,解码器将尝试解码 GQLErrors,这是 graphQL 在您的查询/突变/模式中出错时返回的。 GQLErrors 符合 Error,并由一系列 GQLError 组成。

示例

来自 graphQL 网站

字段

GQL {
    GQLNode("hero") {
        "name"
    }
}
query {
  hero {
    name
  }
}
GQL {
    GQLNode("hero") {
        "name"
        GQLNode("friends") {
            "name"
        }
    }
}
query {
  hero {
    name
    friends {
      name
    }
  }
}

参数

GQL {
    GQLNode("human") {
        "name"
        GQLEmpty("height")
            .withArgument("unit", value: "FOOT")
    }
    .withArgument("id", value: "1000")
}
query {
  human(id: "1000") {
    name
    height(unit: "FOOT")
  }
}

别名](https://graphql.net.cn/learn/queries/#aliases)

GQL {
    GQLNode("hero", alias: "empireHero") {
        "name"
    }
    .withArgument("episode", value: "EMPIRE")
    GQLNode("hero", alias: "jediHero") {
        "name"
    }
    .withArgument("episode", value: "JEDI")
}
query {
  empireHero: hero(episode: "EMPIRE") {
    name
  }
  jediHero: hero(episode: "JEDI") {
    name
  }
}

片段

struct Character: GQLFragmentable {
    static let fragmentName = "comparisonFields"
    static var graqhQl: GraphQLExpression {
        "name"
        "appearsIn"
        GQLNode("friends") {
            "name"
        }
    }
}

GQL {
    GQLNode("hero", alias: "leftComparison") {
        GQLFragment(Character.self)
    }
    .withArgument("episode", value: "EMPIRE")
    GQLNode("hero", alias: "rightComparison") {
        GQLFragment(Character.self)
    }
    .withArgument("episode", value: "JEDI")
}
query {
  leftComparison: hero(episode: "EMPIRE") {
    ...comparisonFields
  }
  rightComparison: hero(episode: "JEDI") {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}
struct Character: GQLFragmentable {
    static let fragmentName = "comparisonFields"
    static var graqhQl: GraphQLExpression {
        "name"
        GQLNode("friendsConnection") {
            "totalCount"
            GQLNode("edges") {
                GQLNode("node") {
                    "name"
                }
            }
        }
        .withArgument("first", variableName: "first")
    }
}

let gql = GQL(name: "HeroComparison") {
    GQLNode("hero", alias: "leftComparison") {
        GQLFragment(Character.self)
    }
    .withArgument("episode", value: "EMPIRE")
    GQLNode("hero", alias: "rightComparison") {
        GQLFragment(Character.self)
    }
    .withArgument("episode", value: "JEDI")
}
query HeroComparison($first: Int) {
  leftComparison: hero(episode: "EMPIRE") {
    ...comparisonFields
  }
  rightComparison: hero(episode: "JEDI") {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}

操作名称

GQL(name: "HeroNameAndFriends") {
    GQLNode("hero") {
        "name"
        GQLNode("friends") {
            "name"
        }
    }
}
query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}

变量

enum Episode: String, GraphQLVariableExpression, Codable {
    case jedi = "JEDI"
}

let gql = GQL(name: "HeroNameAndFriends") {
    GQLNode("hero") {
        "name"
        GQLNode("friends") {
            "name"
        }
    }
    .withArgument("episode", variableName: "episode")
}
.withVariable("episode", value: Episode.jedi as Episode?) // withouth `as Episode` it would output as `Episode!`
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

variables:
{
  "episode": "JEDI"
}

默认变量

// this example doesnt make sense, you would just provide a default value with the optional
enum Episode: String, GraphQLVariableExpression, Codable {
    case jedi = "JEDI"
}

let optional: Episode? = nil

GQL(name: "HeroNameAndFriends") {
    GQLNode("hero") {
        "name"
    }
}
.withVariable("episode", value: optional ?? .jedi)
query HeroNameAndFriends($episode: Episode!) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

variables:
{
  "episode": "JEDI"
}

指令

enum Episode: String, GraphQLVariableExpression, Decodable {
    case jedi = "JEDI"
}

let withFriends = GQLVariable(name: "withFriends", value: false as Bool?)

let gql = GQL(name: "HeroNameAndFriends") {
    GQLNode("hero") {
        "name"
        GQLNode("friends") {
            "name"
        }
        .includeIf(withFriends)
    }
    .withArgument("episode", variableName: "episode")
}
.withVariable("episode", value: Episode.jedi as Episode?)
.withVariable(withFriends as GQLVariable)
query Hero($episode: Episode, $withFriends: Boolean) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}

variables:
{
  "episode": "JEDI",
  "withFriends": false
}
*/

突变

struct ReviewInput: GraphQLVariableExpression, Decodable {
    let stars: Int
    let commentary: String

    static var stub: Self { .init(stars: 5, commentary: "This is a great movie!") }
}

enum Episode: String, GraphQLVariableExpression, Decodable {
    case jedi = "JEDI"
}

let variable = GQLVariable(name: "ep", value: Episode.jedi)

let gql = GQL(.mutation, name: "CreateReviewForEpisode") {
    GQLNode("createReview") {
        "stars"
        "commentary"
    }
    .withArgument("episode", variable: variable)
    .withArgument("review", variableName: "review")
}
.withVariable(variable)
.withVariable("review", value: ReviewInput.stub)
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}

variables:
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}

行内片段 TODO: 实现

高级示例

struct T1: GraphQLVariableExpression, Decodable, Equatable {
    let float: Float
    let int: Int
    let string: String
}

struct T2: GraphQLVariableExpression, Decodable, Equatable {
    let nested: NestedT2
    let temperature: Double
    let weather: String?

    struct NestedT2: Codable, Equatable {
        let name: String
        let active: Bool
    }
}

struct MyFragment: GQLFragmentable, GQLCodable {
    let p1: String
    let p2: String
    let p3: String

    enum CodingKeys: String, CodingKey, CaseIterable {
        case p1
        case p2
        case p3 = "hithere"
    }
}

let t1 = T1(float: 1.5, int: 1, string: "cool name")
let t2 = T2(nested: .init(name: "taylor", active: true), temperature: 2.5, weather: "pretty great")
let rev: String? = "this is great"

let gql = GQL(name: "MyCoolQuery") {
    GQLNode("first", alias: "realFirst") {
        "hello"
        "there"
        MyFragment.asFragment()
        GQLNode("inner") {
            GQLFragment(name: "adhoc", type: "MyFragment") {
                "p1"
                "p2"
            }
            GQLEmpty("cool")
                .skipIf("cool")
            GQLNode("supernested") {
                GQLFragment(MyFragment.self)
            }
            .withArgument("t2", variableName: "type2")
        }
        .withArgument("name", value: "taylor")
        .withArgument("age", value: 666)
        .withArgument("fraction", value: 2.59)
        .withArgument("rev", variableName: "review")
    }
    .withArgument("t1", variableName: "type1")
}
.withVariable("type1", value: t1)
.withVariable("type2", value: t2)
.withVariable("review", value: rev)
.withVariable("cool", value: true)
query MyCoolQuery($cool: Boolean!, $review: String, $type1: T1!, $type2: T2!) {
    realFirst: first(t1: $type1) {
        hello
        there
        ...myfragment
        inner(age: 666, fraction: 2.59, name: \"taylor\", rev: $review) {
            ...adhoc
            cool @skip(if: $cool)
            supernested(t2: $type2) {
                ...myfragment
            }
        }
    }
}

fragment adhoc on MyFragment { p1 p2 } fragment myfragment on MyFragment { p1 p2 hithere }
{
  "query": "query MyCoolQuery($cool: Boolean!, $review: String, $type1: T1!, $type2: T2!) { realFirst: first(t1: $type1) { hello there ...myfragment inner(age: 666, fraction: 2.59, name: \"taylor\", rev: $review) { ...adhoc cool @skip(if: $cool) supernested(t2: $type2) { ...myfragment } } } } fragment adhoc on MyFragment { p1 p2 } fragment myfragment on MyFragment { p1 p2 hithere }",
  "variables": {
    "type2": {
      "nested": { "name": "taylor", "active": true },
      "temperature": 2.5,
      "weather": "pretty great"
    },
    "type1": { "float": 1.5, "int": 1, "string": "cool name" },
    "review": "this is great",
    "cool": true
  }
}