/sanitized

Safely extract and validate Vapor models from requests.

Primary LanguageSwiftMIT LicenseMIT

Sanitized

Swift Version Vapor Version Circle CI codebeat badge codecov Readme Score GitHub license

Safely extract and validate Vapor models from requests.

📦 Installation

Update your Package.swift file.

.Package(url: "https://github.com/nodes-vapor/sanitized.git", majorVersion: 1)

Getting started 🚀

import Sanitized

Before you're able to extract your model from a request it needs to conform to the protocol Sanitizable. To be conferment all you need to do is add a [String] named permitted with a list of keys you wish to allow.

User.swift

struct User: Model, Sanitizable {
    var id: Node?
    var name: String
    var email: String
    
    // will only allow the keys `name` and `email`
    static var permitted = ["name", "email"]
    
    init(node: Node, in context: Context) throws {
        id = node["id"]
        name = try node.extract("name")
        email = try node.extract("email")
    }
    
    //...
}

Now that you have a conforming model, you can safely extract it from a Request.

Request body

{
  "id": 10,
  "name": "Brett",
  "email": "test@tested.com"
}

Main.swift

drop.post("users") { req in 
    var user: User = try req.extractModel()
    print(user.id == nil) // prints `true`
    try user.save()
    return user
}

Updating/patching existing models 🖇

Just like model extraction, securely updating a model with data from a request is a trivial process.

drop.post("users", User.self) { req, user in
    var updatedUser = try req.patchModel(user)
    try updatedUser.save()
}

Updating model with Id

If you don't have an instance of the model you wish to update you can have Sanitize fetch and update the model for you.

drop.post("users", Int.self) { req, userId in
    var user: User = try req.patchModel(userId)
    try user.save()
}

Validation ✅

This package doesn't specifically provide any validation tools, but it is capable of running your validation suite for you. Thusly, simplifying the logic in your controllers. Sanitized has two ways of accomplishing this: pre and post validation.

Pre-init validation

This type of validation is run before the model is initialized and is checked against the request's JSON. This type of field is useful for when you only want to check if a field exists before continuing.

Create a preValidation check by overriding the default implementation in your Sanitizable model.

static func preValidate(data: JSON) throws {
    // we only want to ensure that `name` exists/
    guard data["name"] != nil else {
      throw MyError.invalidRequest("Name not provided.")
    }
}

Post-init validation

This type of validation is run after the model has been initialized is useful for checking the content of fields while using Swift-native types.

Create a postValidation check by overriding the default implementation in your Sanitizable model.

func postValidate() throws {
    guard email.count > 8 else {
        throw Abort.custom(
            status: .badRequest,
            message: "Email must be longer than 8 characters."
        )
    }
}

Overriding error thrown on failed init 🔨

The error thrown by a failed Node.extract will be turned into a 500 Internal Server Error if not caught and changed before being caught by Vapor's AbortMiddleware. By default, this package will catch that error and convert it into a 400 Bad Request. If you wish to disable this for development environments or throw your own error, you can override the following default implementation:

static func updateThrownError(_ error: Error) -> AbortError {
    // recreates the default behavior of `AbortMiddleware`.
    return Abort.custom(
        status: .internalServerError,
        message: "\(error)"
    )
}

🏆 Credits

This package is developed and maintained by the Vapor team at Nodes. The package owner for this project is Brett.

📄 License

This package is open-sourced software licensed under the MIT license