Interakcja z API GraphQL
Interakcja z API GraphQLZarządzanie payloadami mutations

Zarządzanie payloadami mutations

Pola mutation można skonfigurować tak, aby zwracały jeden z 2 różnych typów encji:

  • Typ obiektu payload
  • Bezpośrednio zmodyfikowaną encję

Typ obiektu payload

Typ obiektu payload zawiera wszystkie dane dotyczące mutation:

  • Status mutation (sukces lub niepowodzenie)
  • Błędy (jeśli istnieją) przy użyciu odrębnych typów GraphQL, lub
  • Pomyślnie zmodyfikowaną encję

Na przykład mutation updatePost zwraca obiekt typu PostUpdateMutationPayload, a my nadal musimy odpytać jego pole post, aby pobrać zaktualizowaną encję post:

mutation UpdatePost {
  updatePost(input: {
    id: 1724,
    title: "New title",
    status: publish
  }) {
    # This is the status of the mutation: SUCCESS or FAILURE
    status
    errors {
      __typename
      ...on ErrorPayload {
        message
      }
    }
    post {
      id
      title
      # This is the status of the post: publish, pending, trash, etc
      status
    }
  }
}

Obiekt payload pozwala nam lepiej reprezentować błędy, a nawet posiadać unikalny typ GraphQL dla każdego rodzaju błędu. Dzięki temu możemy prezentować różne reakcje na różne błędy w aplikacji, poprawiając tym samym doświadczenie użytkownika.

W powyższym przykładzie, jeśli operacja zakończyła się sukcesem, otrzymamy:

{
  "data": {
    "updatePost": {
      "status": "SUCCESS",
      "errors": null,
      "post": {
        "id": 1724,
        "title": "Some title",
        "status": "publish"
      }
    }
  }
}

Jeśli użytkownik nie jest zalogowany, otrzymamy:

{
  "data": {
    "updatePost": {
      "status": "FAILURE",
      "errors": [
        {
          "__typename": "UserIsNotLoggedInErrorPayload",
          "message": "You must be logged in to create or update custom posts"
        }
      ],
      "post": null
    }
  }
}

Jeśli użytkownik nie ma uprawnień do edycji postów, otrzymamy:

{
  "data": {
    "updatePost": {
      "status": "FAILURE",
      "errors": [
        {
          "__typename": "LoggedInUserHasNoEditingCustomPostCapabilityErrorPayload",
          "message": "Your user doesn't have permission for editing custom posts."
        }
      ],
      "post": null
    }
  }
}

W tym trybie schemat GraphQL będzie zawierał wiele dodatkowych typów MutationPayload, MutationErrorPayloadUnion i ErrorPayload, więc będzie miał większy rozmiar:

Schemat GraphQL z typami obiektów payload dla mutations

Odpytywanie obiektów payload mutations

Każda mutation w schemacie posiada odpowiednie pole do odpytywania jej niedawno utworzonych obiektów payload, o nazwie {mutationName}MutationPayloadObjects.

Te pola obejmują:

  • addCommentToCustomPostMutationPayloadObjects (dla addCommentToCustomPost)
  • createCustomPostMutationPayloadObjects (dla createCustomPost)
  • createMediaItemMutationPayloadObjects (dla createMediaItem)
  • createPageMutationPayloadObjects (dla createPage)
  • createPostMutationPayloadObjects (dla createPost)
  • removeFeaturedImageFromCustomPostMutationPayloadObjects (dla removeFeaturedImageFromCustomPost)
  • replyCommentMutationPayloadObjects (dla replyComment)
  • setCategoriesOnPostMutationPayloadObjects (dla setCategoriesOnPost)
  • setFeaturedImageOnCustomPostMutationPayloadObjects (dla setFeaturedImageOnCustomPost)
  • setTagsOnPostMutationPayloadObjects (dla setTagsOnPost)
  • updateCustomPostMutationPayloadObjects (dla updateCustomPost)
  • updatePageMutationPayloadObjects (dla updatePage)
  • updatePostMutationPayloadObjects (dla updatePost)

Te pola umożliwiają nam pobieranie wyników mutations wykonanych przy użyciu @applyField podczas iterowania po elementach tablicy.

Na przykład poniższe zapytanie duplikuje posty zbiorczo:

query GetPostsAndExportData
{
  postsToDuplicate: posts {
    title
    rawContent
    excerpt
 
    # Already create (and export) the inputs for the mutation
    postInput: _echo(value: {
      title: $__title
      contentAs: {
        html: $__rawContent
      },
      excerpt: $__excerpt
    })
      @export(as: "postInput", type: LIST)
      @remove
  }
}
 
mutation CreatePosts
  @depends(on: "GetPostsAndExportData")
{
  createdPostMutationPayloadObjectIDs: _echo(value: $postInput)
    @underEachArrayItem(
      passValueOnwardsAs: "input"
    )
      @applyField(
        name: "createPost"
        arguments: {
          input: $input
        },
        setResultInResponse: true
      )
    @export(as: "createdPostMutationPayloadObjectIDs")
}
 
query DuplicatePosts
  @depends(on: "CreatePosts")
{
  createdPostMutationObjectPayloads: createPostMutationPayloadObjects(input: {
    ids: $createdPostMutationPayloadObjectIDs
  }) {
    status
    errors {
      __typename
      ...on ErrorPayload {
        message
      }
    }
    post {
      id
      title
      rawContent
      excerpt
    }
  }
}

Domyślnie te pola nie są dodawane do schematu GraphQL. W tym celu musimy wybrać opcję "Use payload types for mutations, and add fields to query those payload objects".

Zmodyfikowana encja

Mutation zwróci bezpośrednio zmodyfikowaną encję w przypadku sukcesu, lub null w przypadku niepowodzenia, a wszelkie komunikaty o błędach będą wyświetlane we wpisie errors najwyższego poziomu odpowiedzi JSON.

Na przykład mutation updatePost zwróci obiekt typu Post:

mutation UpdatePost {
  updatePost(input: {
    id: 1724,
    title: "New title",
    status: publish
  }) {
    id
    title
    status
  }
}

Jeśli operacja zakończyła się sukcesem, otrzymamy:

{
  "data": {
    "updatePost": {
      "id": 1724,
      "title": "Some title",
      "status": "publish"
    }
  }
}

W przypadku błędów pojawią się one we wpisie errors odpowiedzi. Na przykład, jeśli użytkownik nie jest zalogowany, otrzymamy:

{
    "errors": [
      {
        "message": "You must be logged in to create or update custom posts'",
        "locations": [
          {
            "line": 2,
            "column": 3
          }
        ]
      }
  ],
  "data": {
    "updatePost": null
  }
}

Musimy zauważyć, że w rezultacie wpis errors najwyższego poziomu będzie zawierał nie tylko błędy składni, walidacji schematu i logiki (np.: nieprzekazanie nazwy argumentu pola, żądanie nieistniejącego pola, lub wywołanie _sendHTTPRequest przy braku połączenia sieciowego), ale także błędy "walidacji treści" (np.: "nie masz uprawnień do modyfikowania tego posta").

Ponieważ nie są dodawane żadne dodatkowe typy, schemat GraphQL będzie wyglądał bardziej przejrzyście:

Schemat GraphQL bez typów obiektów payload dla mutations

Zarządzanie typem obiektu payload dla mutations

Zobaczmy, jak obsługiwać pierwszą opcję, typ obiektu payload.

Mutations w schemacie zwracają pewien obiekt payload, który dostarcza wszelkich błędów wynikających z mutation, lub zmodyfikowany obiekt w przypadku sukcesu (te 2 właściwości są najprawdopodobniej wzajemnie wykluczające: albo errors, albo object będzie miało wartość, a drugie będzie null).

Błędy są dostarczane za pośrednictwem pewnego typu "ErrorPayloadUnion", zawierającego wszystkie możliwe błędy dla danej mutation. Każdy możliwy błąd jest pewnym typem "ErrorPayload", który implementuje interfejs ErrorPayload.

Na przykład operacja updatePost zwraca PostUpdateMutationPayload, który zawiera następujące pola:

  • status: czy operacja zakończyła się sukcesem, czy nie, z wartością SUCCESS lub FAILURE
  • post i postID: zaktualizowany obiekt post i jego ID, jeśli aktualizacja zakończyła się sukcesem
  • errors: lista CustomPostUpdateMutationErrorPayloadUnion, jeśli aktualizacja się nie powiodła.

Typ union CustomPostUpdateMutationErrorPayloadUnion zawiera listę wszystkich możliwych błędów, które mogą wystąpić podczas modyfikowania custom post:

  • CustomPostDoesNotExistErrorPayload
  • GenericErrorPayload
  • LoggedInUserHasNoEditingCustomPostCapabilityErrorPayload
  • LoggedInUserHasNoPermissionToEditCustomPostErrorPayload
  • LoggedInUserHasNoPublishingCustomPostCapabilityErrorPayload
  • UserIsNotLoggedInErrorPayload

Typ błędu GenericErrorPayload jest zawarty we wszystkich typach "ErrorPayloadUnion". Jest używany zawsze, gdy nie można wskazać konkretnej przyczyny błędu, na przykład gdy wp_update_post po prostu zwraca WP_Error. Ten typ udostępnia dwa dodatkowe pola: code i data.

Następnie, aby wykonać mutation updatePost, możemy wykonać:

mutation UpdatePost(
  $postId: ID!
  $title: String!
) {
  updatePost(
    input: {
      id: $postId,
      title: $title,
    }
  ) {
    status
    errors {
      __typename
      ...on ErrorPayload {
        message
      }
      ...on GenericErrorPayload {
        code
      }
    }
    post {
      id
      title
    }
  }
}

Jeśli operacja zakończyła się sukcesem, otrzymamy:

{
  "data": {
    "updatePost": {
      "status": "SUCCESS",
      "errors": null,
      "post": {
        "id": 1724,
        "title": "This incredible title"
      }
    }
  }
}

Jeśli użytkownik nie jest zalogowany, otrzymamy:

{
  "data": {
    "updatePost": {
      "status": "FAILURE",
      "errors": [
        {
          "__typename": "UserIsNotLoggedInErrorPayload",
          "message": "You must be logged in to create or update custom posts"
        }
      ],
      "post": null
    }
  }
}

Jeśli użytkownik nie ma uprawnień do edycji postów, otrzymamy:

{
  "data": {
    "updatePost": {
      "status": "FAILURE",
      "errors": [
        {
          "__typename": "LoggedInUserHasNoEditingCustomPostCapabilityErrorPayload",
          "message": "Your user doesn't have permission for editing custom posts."
        }
      ],
      "post": null
    }
  }
}