apple / swift-nio

Event-driven network application framework for high performance protocol servers & clients, non-blocking.
https://swiftpackageindex.com/apple/swift-nio/documentation
Apache License 2.0
7.97k stars 652 forks source link

cannot convert value of type 'ByteBuffer' to expected argument type 'Data' #2154

Closed naoya-ashizawa closed 2 years ago

naoya-ashizawa commented 2 years ago

Expected behavior

When I tried to get data from the API with AsyncHTTPClient and parse it into Codable, I had a problem.

Actual behavior

I got an error when compiling the following code.

swift code

let response = try JSONDecoder().decode(R.Response.self, from: buffer)

error message

error: cannot convert value of type 'ByteBuffer' to expected argument type 'Data'

Steps to reproduce

  1. Create Package.json and add library
touch Package.json

and add async-http-client on dependencies

  1. Create Dockerfile and docker-compose.yml

Dockerfile

ARG swift_version=5.4
ARG ubuntu_version=bionic
ARG base_image=swift:$swift_version-$ubuntu_version
FROM $base_image
# needed to do again after FROM due to docker limitation
ARG swift_version
ARG ubuntu_version

# set as UTF-8
RUN apt-get update && apt-get install -y locales locales-all
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US.UTF-8

# dependencies
RUN apt-get update && apt-get install -y wget
RUN apt-get update && apt-get install -y lsof dnsutils netcat-openbsd net-tools curl jq # used by integration tests

# ruby and jazzy for docs generation
RUN apt-get update && apt-get install -y ruby ruby-dev libsqlite3-dev build-essential
# jazzy no longer works on xenial as ruby is too old.
RUN if [ "${ubuntu_version}" = "focal" ] ; then echo "gem: --no-document" > ~/.gemrc ; fi
RUN if [ "${ubuntu_version}" = "focal" ] ; then gem install jazzy ; fi

# tools
RUN mkdir -p $HOME/.tools
RUN echo 'export PATH="$HOME/.tools:$PATH"' >> $HOME/.profile

# swiftformat (until part of the toolchain)

ARG swiftformat_version=0.40.12
RUN git clone --branch $swiftformat_version --depth 1 https://github.com/nicklockwood/SwiftFormat $HOME/.tools/swift-format
RUN cd $HOME/.tools/swift-format && swift build -c release
RUN ln -s $HOME/.tools/swift-format/.build/release/swiftformat $HOME/.tools/swiftformat

EXPOSE 3000

docker-compose.yml

version: '3'
  mylib:
      &mylib
    build: .
    volumes:
      - .:/mylib
    working_dir: /mylib
    tty: true

  test:
    <<: *mylib
    command: /bin/bash -xcl "swift test --parallel -Xswiftc -warnings-as-errors $${SANITIZER_ARG-}"

  shell:
    <<: *mylib
    entrypoint: /bin/bash
  1. build docker
docker-compose build
  1. swift test
    docker-compose run test

If possible, minimal yet complete reproducer code (or URL to code)

import AsyncHTTPClient

/// Response Example
struct MyResponse: Codable {}

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
defer {
    try? httpClient.syncShutdown()
}

var request = try HTTPClient.Request(url: "https://apple.com/", method: .POST)
request.headers.add(name: "User-Agent", value: "Swift HTTPClient")
request.body = .string("some-body")

httpClient.execute(request: request).whenComplete { result in
    switch result {
    case .failure(let error):
        // process error
    case .success(let response):
        if response.status == .ok {
            guard let buffer = response.body else { return }
            //error occured
            let _ = try JSONDecoder().decode(MyResponse.self, from: buffer)
        } else {
            // handle remote error
        }
    }
}

SwiftNIO version/commit hash

swift-nio 2.40.0
async-http-client 1.10.0
dnadoba commented 2 years ago

Hi @naoya-ashizawa, The way to decode JSON from an ByteBuffer is to use NIOFoundationCompat from swift-nio.

If you not already have you should add swift-nio as a dependency to your Package.swift

dependencies: [
    .package(url: "https://github.com/apple/swift-nio.git", from: "2.40.0"),
]

and afterwards add NIOFoundationCompat as a dependency of your target e.g:

.target(
    name: "NameOfYourTarget",
    dependencies: [
        ...
        .product(name: "NIOFoundationCompat", package: "swift-nio"),
    ], 
),

NIOFoundationCompat adds a method overload to JSONDecoder.deocde(_:from) which takes a ByteBuffer instead of Data and your code will just work as is if you now import NIOFoundationCompat.

We also have complete example but which uses async/await in the /Examples/GetJSON folder of the repository. You can clone or download this repository and play around with the Example by opening the /Examples/Package.swift.