草庐IT

swift - 使 Vapor API 响应符合 JSON API 规范

coder 2023-09-05 原文

我有一个用 Vapor 编写的 API。我想遵循 JSON API 规范。

我很难理解如何以正确的格式创建我的响应对象。

例如,我希望我的回复结构如下...

{
  "links": {
    "self": "http://example.com/dish",
    "next": "http://example.com/dish?page=2",
    "last": "http://example.com/dish?page=10"
  },
  "data": [{
    "title": "Spag Bol",
    "course": "main",
    "description": "BasGetti",
    "price": 3.9900000000000002
  },
  {
    "title": "Ice Cream",
    "course": "desert",
    "description": "Vanilla",
    "price": 0.98999999999999999
  }]
}

如果 POST 到这个端点(伪代码),我可以非常简单地返回数据的内容

router.post(Dish.self, at: "api/dish") { req, data -> Future<Dish> in
    return Future.map(on: req, { () -> Dish in
        data.id = 001
        return data
    })
}

我尝试创建一个 ApiResponse 类并传入数据,这样我就可以构建响应,但这并没有解决错误 Cannot convert return expression of type 'ApiResonse' to return type '菜'

   router.post(Dish.self, at: "api/dish") { req, data -> Future<Dish> in
        return Future.map(on: req, { () -> Dish in
            data.id = 001
            return ApiResonse(links: Links(self: "http://google.com", next: "http://google.com", last: "http://google.com"), data: data)
        })
    }

我不确定我该怎么做。这些是尝试过的类(class)

final class Dish: Content {
    var id: Int?
    var title: String
    var description: String
    var course: String
    var price: Double

    init(title: String, description: String, course: String, price: Double) {
        self.title = title
        self.description = description
        self.course = course
        self.price = price
    }
}

struct Links {
    var `self`: String?
    var next: String?
    var last: String?
}

class ApiResonse {
    var links: Links?
    var data: Any

    init(links: Links, data: Any) {
        self.links = links
        self.data = data
    }
}

我是否需要使用泛型来设置响应类?谁能举个例子?

最佳答案

  1. 每个 classstruct在复合对象中 ApiResponse需要遵守 Content协议(protocol)。 Content协议(protocol)包括 Codable JSON解码和编码协议(protocol)。

  2. 注意 Any 符合Codable协议(protocol),因此 Any 不能用作响应的任何组成部分。参见 Vapor 3 Docs: "Using Content"Vapor 4 Docs: "Content"获取更多详细信息。

    Vapor 3: all content types (JSON, protobuf, URLEncodedForm, Multipart, etc) are treated the same. All you need to parse and serialize content is a Codable class or struct.

    Vapor 4: Vapor's content API allows you to easily encode / decode Codable structs to / from HTTP messages.

  3. 完全符合 Content 的对象或复合对象可以用作 ResponseEncodable响应。

  4. ApiResponse当每个路由端点解析为特定的 Content 时,模型可以是通用的协议(protocol)兼容类型

带有以下代码的示例项目在 GitHub: VaporExamplesLab/Example-SO-VaporJsonResponse 上.

示例模型

struct Dish: Content {
    var id: Int?
    var title: String
    var description: String
    var course: String
    var price: Double
    
    init(id: Int? = nil, title: String, description: String, course: String, price: Double) {
        self.id = id
        self.title = title
        self.description = description
        self.course = course
        self.price = price
    }
}

struct Links: Content {
    var current: String?
    var next: String?
    var last: String?
}

struct ApiResponse: Content {
    var links: Links?
    var dishes: [Dish]
    
    init(links: Links, dishes: [Dish]) {
        self.links = links
        self.dishes = dishes
    }
}

示例 POST : 返回 ApiResponse

router.post(Dish.self, at: "api/dish") { 
    (request: Request, dish: Dish) -> ApiResponse in
    var dishMutable = dish
    dishMutable.id = 001
    
    var links = Links()
    links.current = "http://example.com"
    links.next = "http://example.com"
    links.last = "http://example.com"

    return ApiResponse(links: links, dishes: [dishMutable])
}

示例 POST : 返回 Future<ApiResponse>

router.post(Dish.self, at: "api/dish-future") { 
    (request: Request, dish: Dish) -> Future<ApiResponse> in
    var dishMutable = dish
    dishMutable.id = 002
    
    var links = Links()
    links.current = "http://example.com"
    links.next = "http://example.com"
    links.last = "http://example.com"
    
    return Future.map(on: request, { 
        () -> ApiResponse in
        return ApiResponse(links: links, dishes: [dishMutable])
    }) 
}

收到 JSON 响应

上述代码的响应主体产生以下内容:

{
  "links": {
    "current": "http://example.com",
    "next": "http://example.com",
    "last": "http://example.com"
  },
  "dishes": [
    {
      "id": 1,
      "title": "Aztec Salad",
      "description": "Flavorful Southwestern ethos with sweet potatos and black beans.",
      "course": "salad",
      "price": 1.82
    }
  ]
}

通用模型

struct ApiResponseGeneric<T> : Content where T: Content { 
    var links: Links?
    var data: T
    
    init(links: Links, data: T) {
        self.links = links
        self.data = data
    }
}

具体路线端点

router.post(Dish.self, at: "api/dish-generic-future") { 
    (request: Request, dish: Dish) -> Future<ApiResponseGeneric<[Dish]>> in
    var dishMutable = dish
    dishMutable.id = 004
    
    var links = Links()
    links.current = "http://example.com"
    links.next = "http://example.com"
    links.last = "http://example.com"
    
    return Future.map(on: request, { 
        () -> ApiResponseGeneric<[Dish]> in
        return ApiResponseGeneric<[Dish]>(links: links, data: [dishMutable])
    }) 
}

关于swift - 使 Vapor API 响应符合 JSON API 规范,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52232746/

有关swift - 使 Vapor API 响应符合 JSON API 规范的更多相关文章

  1. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

  2. ruby-on-rails - 在 Ruby on Rails 中发送响应之前如何等待多个异步操作完成? - 2

    在我做的一些网络开发中,我有多个操作开始,比如对外部API的GET请求,我希望它们同时开始,因为一个不依赖另一个的结果。我希望事情能够在后台运行。我找到了concurrent-rubylibrary这似乎运作良好。通过将其混合到您创建的类中,该类的方法具有在后台线程上运行的异步版本。这导致我编写如下代码,其中FirstAsyncWorker和SecondAsyncWorker是我编写的类,我在其中混合了Concurrent::Async模块,并编写了一个名为“work”的方法来发送HTTP请求:defindexop1_result=FirstAsyncWorker.new.async.

  3. ruby - gem 规范失败 - 2

    我正在为毕业设计开发GEM,TravisCI构建不断失败。这是我在Travis上的链接:https://travis-ci.org/ricardobond/perpetuus/builds/8709218构建错误是:$bundleexecrakerakeaborted!Don'tknowhowtobuildtask'default'/home/travis/.rvm/gems/ruby-1.9.3-p448/bin/ruby_noexec_wrapper:14:in`eval'/home/travis/.rvm/gems/ruby-1.9.3-p448/bin/ruby_noexec_

  4. ruby-on-rails - find_all 数组中符合条件的元素? - 2

    我有一个哈希条目数组,并希望根据传递给函数的参数进行过滤。如果散列中有三个值,A、B和C,我想做类似的事情:data=[{A:'a1',B:'b1',C:'c1'},{A:'a1',B:'b2',C:'c1'},{A:'a1',B:'b2',C:'c2'},{A:'a2',B:'b1',C:'c1'},{A:'a2',B:'b2',C:'c1'}]data.find_all{|d|d[:A].include?params[:A]}.find_all{|d|d[:B].include?params[:B]}.find_all{|d|d[:C].include?params[:C]}找到所

  5. ruby - 如何找出所有数组元素是否都符合某个条件? - 2

    我有一个大数组,我需要知道它的所有元素是否都能被2整除。我是这样做的,但是有点丑:_true=truearr.each{|e|(e%2).zero?||_true=false}if_true==true#...end如何在没有额外循环/赋值的情况下做到这一点? 最佳答案 这样就可以了。arr.all?(&:even?) 关于ruby-如何找出所有数组元素是否都符合某个条件?,我们在StackOverflow上找到一个类似的问题: https://stackov

  6. ruby - 如何禁止在 RSpec 中显示挂起(跳过)的规范? - 2

    我有几个跳过的规范。Pending:(Failureslistedhereareexpectedanddonotaffectyoursuite'sstatus)1)...#Notyetimplemented#./spec/requests/request_spec.rb:22如何抑制未决规范的输出? 最佳答案 您可以添加以下配置选项以从运行中过滤掉所有待处理的规范:RSpec.configuredo|config|config.filter_run_excludingskip:trueend此外,here是一个更详细的抑制输出的建议

  7. ruby-on-rails - 从帮助器规范中 stub 一个帮助器方法 - 2

    我正在构建Rails应用程序并使用RSpec制定测试。我为我正在创建的名为current_link_to的方法编写了测试。此方法应该检查当前页面是否对应于我传递给它的路径,并将current类添加到生成的链接中,以防它匹配。这是规范:require"spec_helper"describeApplicationHelperdodescribe"#current_link_to"dolet(:name){"Products"}let(:path){products_path}let(:rendered){current_link_to(name,path)}context"whenthe

  8. ruby-on-rails - Rails 在记录 200 OK 后在做什么? (调试响应时间慢) - 2

    我试图在我的RubyonRails应用程序中调试一个极其缓慢的请求调用。我已设法根据自己的喜好优化Controller方法,Rails的日志告诉我它已在XX毫秒内完成操作(Completed200OKin5049ms(Views:34.9ms|ActiveRecord:76.3ms)).但是,在加载页面时,在浏览器中实际呈现任何内容之前打印此消息很长;最多约15秒的等待时间。Rackmini-profiler证实了这一点,告诉我GET操作(不计算完成Controller操作所花费的时间)花费了14秒左右。(分析器还确认Controller操作的执行时间约为5秒)。我可以接受Contro

  9. ruby-on-rails - 在 RSpec 中编写请求规范的正确方法是什么? - 2

    tl;dr:跳到最后一段最近一直在尝试使用RSpec的requestspecs做一些更有针对性的测试。我的测试主要是这样的:通用cucumber功能规范,即用户转到带有评论的帖子,对评论点赞,作者获得积分modelspecs当模型实际上具有某些功能时,即User#upvote(comment)controllerspecs我在其中stub了大部分内容,只是试图确保代码按照我期望的方式运行viewspecs当View中有一些复杂的东西时,例如仅在用户尚未投票时呈现upvote链接,这些被stub为好吧问题是当我有一些导致错误的特定场景时,一切似乎都在我无法重现它的模型/View层中工作。

  10. ruby - 带有 header 的 Sinatra 流式响应 - 2

    我想通过Sinatra应用程序代理远程文件。这需要将带有header的HTTP响应从远程源流式传输回客户端,但我不知道如何在Net::HTTP#提供的block内使用流式API时设置响应header获取响应。例如,这不会设置响应头:get'/file'dostreamdo|out|uri=URI("http://manuals.info.apple.com/en/ipad_user_guide.pdf")Net::HTTP.get_response(uri)do|file|headers'Content-Type'=>file.header['Content-Type']file.re

随机推荐