Search code examples
vapor

How does request.parameters.next work in Vapor mechanism?


When using Vapor to build a API, always use a method to fetch a object,

request.parameters.next(type.self)

It seems a method to extract the url parameter, why it return the result from the database ? Could not found any clues from the framework's source code. How can I find the answer ? Thanks.


Solution

  • This how Parameter protocol looks like

    public protocol Parameter {
        associatedtype ResolvedParameter
    
        static var routingSlug: String { get }
    
        static func resolveParameter(_ parameter: String, on container: Container) throws -> ResolvedParameter
    }
    

    when you conform anything to Parameter you should implement this function

    static func resolveParameter(_ parameter: String, on container: Container) throws -> ResolvedParameter
    

    which should parse string parameter from URL and return some object as a result. And as Parameter is generic you can return any type you want.

    Let's write an extension to conform Date to Parameter

    extension Date: Parameter {
        public static func resolveParameter(_ parameter: String, on container: Container) throws -> Date {
            guard let timeIntervalSince1970 = TimeInterval(parameter) else {
                throw Abort(.notAcceptable, reason: "Unable to parse \"\(parameter)\" parameter")
            }
            return Date(timeIntervalSince1970: timeIntervalSince1970)
        }
    }
    

    Parameter's method parses String parameter from URL and returnsDate, but you also can return something else, e.g. TimeInterval since Parameter is generic protocol

    extension Date: Parameter {
        public static func resolveParameter(_ parameter: String, on container: Container) throws -> TimeInterval {
            guard let timeIntervalSince1970 = TimeInterval(parameter) else {
                throw Abort(.notAcceptable, reason: "Unable to parse \"\(parameter)\" parameter")
            }
            return timeIntervalSince1970
        }
    }
    

    When you conform your Fluent model to Parameter it already have an implementation for Parameter protocol in its extensions, that's why you shouldn't declare resolveParameter function manually.

    Let's take a look how it works if you declare resolveParameter method manually in e.g. User model

    extension User {
        public static func resolveParameter(_ parameter: String, on container: Container) throws -> Future<User> {
            // e.g. User's primary key is UUID
            guard let id = UUID(parameter) else {
                throw Abort(.notAcceptable, reason: "Unable to parse \"\(parameter)\" into UUID")
            }
            // getting database connection from pool
            return container.requestPooledConnection(to: .psql).flatMap { conn in
                // querying user by provided primary key
                return User.query(on: conn)
                           .filter(\.id == id)
                           .first()
                           .unwrap(or: Abort(.notFound, reason: "Unable to find user by provided primary key"))
                           .always {
                    // will release connection in any case
                    try? container.releasePooledConnection(conn, to: .psql)
                }
            }
        }
    }
    

    So as you can see you can implement resolveParameter to return anything you want.