mirror of https://github.com/vapor/docs.git
Merge branch 'main' into translation/add-authentication-german-translation
This commit is contained in:
commit
34ceebe798
|
|
@ -11,5 +11,6 @@ Languages:
|
|||
- [ ] Dutch
|
||||
- [ ] Italian
|
||||
- [ ] Spanish
|
||||
- [ ] Polish
|
||||
|
||||
Assigned to @vapor/translators - please submit a PR with the relevant updates and check the box once merged. Please ensure you tag your PR with the `translation-update` so it doesn't create a new issue!
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ final class MyTests: XCTestCase {
|
|||
|
||||
## 可测试的应用程序
|
||||
|
||||
使用 `.testing` 环境初始化一个 `Application` 实例。你必须在此应用程序初始化之前,调用 `app.shutdown()`。
|
||||
使用 `.testing` 环境初始化一个 `Application` 的实例。在此应用程序被销毁之前,你必须调用 `app.shutdown()`。关闭操作是为了释放应用程序所占用的资源。特别重要的是释放应用程序在启动时请求的线程。如果你在每个单元测试后没有调用 `shutdown()` 方法,可能会在为 `Application` 的新实例分配线程时导致测试套件崩溃,出现先决条件失败。
|
||||
|
||||
```swift
|
||||
let app = Application(.testing)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
# Cliente
|
||||
|
||||
La API cliente de Vapor te permite hacer llamadas HTTP a recursos externos. Está hecha en [async-http-client](https://github.com/swift-server/async-http-client) y se integra con la API [content](content.md).
|
||||
|
||||
## Descripción
|
||||
|
||||
Puedes acceder al cliente por defecto mediante `Application`, o en un controlador de rutas mediante `Request`.
|
||||
|
||||
```swift
|
||||
app.client // Cliente
|
||||
|
||||
app.get("test") { req in
|
||||
req.client // Cliente
|
||||
}
|
||||
```
|
||||
|
||||
El cliente de la aplicación es útil para realizar peticiones HTTP durante la configuración. Si realizas las peticiones HTTP en un controlador de rutas, usa siempre el cliente de la petición (request).
|
||||
|
||||
### Métodos
|
||||
|
||||
Para realizar una petición `GET`, proporciona la URL deseada al método de conveniencia `get`.
|
||||
|
||||
```swift
|
||||
let response = try await req.client.get("https://httpbin.org/status/200")
|
||||
```
|
||||
|
||||
Existen métodos para cada acción HTTP, como `get`, `post`, y `delete`. La respuesta del cliente es devuelta como un futuro y contiene el estado (status) HTTP, las cabeceras y el cuerpo de la petición.
|
||||
|
||||
### Content
|
||||
|
||||
La API [content](content.md) de Vapor está disponible para el manejo de datos en las peticiones y respuestas del cliente. Para codificar contenido, parámetros de petición o añadir cabeceras a la petición, usa el closure `beforeSend`.
|
||||
|
||||
```swift
|
||||
let response = try await req.client.post("https://httpbin.org/status/200") { req in
|
||||
// Codifica la cadena de consulta (query) a la petición URL.
|
||||
try req.query.encode(["q": "test"])
|
||||
|
||||
// Codifica un JSON en el cuerpo de la petición.
|
||||
try req.content.encode(["hello": "world"])
|
||||
|
||||
// Añade una cabecera de autenticación a la petición.
|
||||
let auth = BasicAuthorization(username: "something", password: "somethingelse")
|
||||
req.headers.basicAuthorization = auth
|
||||
}
|
||||
// Controla la respuesta.
|
||||
```
|
||||
|
||||
También puedes decodificar el cuerpo de la respuesta usando `Content` de manera similar:
|
||||
|
||||
```swift
|
||||
let response = try await req.client.get("https://httpbin.org/json")
|
||||
let json = try response.content.decode(MyJSONResponse.self)
|
||||
```
|
||||
|
||||
Si estás utilizando futuros, puedes usar `flatMapThrowing`:
|
||||
|
||||
```swift
|
||||
return req.client.get("https://httpbin.org/json").flatMapThrowing { res in
|
||||
try res.content.decode(MyJSONResponse.self)
|
||||
}.flatMap { json in
|
||||
// Usa JSON aquí
|
||||
}
|
||||
```
|
||||
|
||||
## Configuración
|
||||
|
||||
Puedes configurar el cliente HTTP subyacente mediante la aplicación.
|
||||
|
||||
```swift
|
||||
// Desactiva el seguimiento de redireccionado automático.
|
||||
app.http.client.configuration.redirectConfiguration = .disallow
|
||||
```
|
||||
|
||||
Ten en cuenta que debes configurar el cliente por defecto _antes_ de usarlo por primera vez.
|
||||
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
# Modelbindung
|
||||
|
||||
Mit der Modelbindung können wir den Inhalt oder die Zeichenfolge einer Serveranfrage an einen vordefiniertes Datenobjekt binden.
|
||||
|
||||
## Grundlagen
|
||||
|
||||
Um das Binden besser zu verstehen, werfen wir einen kurzen Blick auf den Aufbau einer solchen Serveranfrage.
|
||||
|
||||
```http
|
||||
POST /greeting HTTP/1.1
|
||||
content-type: application/json
|
||||
content-length: 18
|
||||
|
||||
{"hello": "world"}
|
||||
```
|
||||
|
||||
Die Angabe _content-type_ in der Kopfzeile gibt Aufschluss über die Art des Inhaltes der Anfrage. Vapor nutzt die Angabe um den richtigen Kodierer zum Binden zu finden.
|
||||
|
||||
Im Beispiel können wir erkennen, dass es sich bei dem Inhalt um JSON-Daten handelt.
|
||||
|
||||
## Binden des Inhalts
|
||||
|
||||
Zum Binden des Inhalts müssen wir zuerst eine Struktur vom Typ *Codable* anlegen. Indem wir das Objekt mit Vapor's Protokoll *Content* versehen, werden neben den eigentlichen Bindungsmethoden, der Typ mitvererbt.
|
||||
|
||||
```swift
|
||||
struct Greeting: Content {
|
||||
var hello: String
|
||||
}
|
||||
```
|
||||
|
||||
Über die Eigenschaft *content* können wir anschließend die Methode *decode(_:)* verwenden.
|
||||
|
||||
```swift
|
||||
app.post("greeting") { req in
|
||||
let greeting = try req.content.decode(Greeting.self)
|
||||
print(greeting.hello) // "world"
|
||||
return HTTPStatus.ok
|
||||
}
|
||||
```
|
||||
|
||||
Die Methode *decode(_:)* benutzt die entsprechende Angabe in der Serveranfrage um den passenden Kodierer aufzurufen.
|
||||
|
||||
Sollte kein passender Kodierer gefunden werden oder die Anfrage keine Angaben zum Inhalt besitzen, wird der Fehler 415 (415 Unsupported Media Type) zurückgeliefert.
|
||||
|
||||
### Unterstützte Medien
|
||||
|
||||
Folgende Medien werden von Vapor standardmäßig unterstützt:
|
||||
|
||||
|Bezeichnung |Feldwert |Typ |
|
||||
|----------------|---------------------------------|-----------------|
|
||||
|JSON |application/json |`.json` |
|
||||
|Multipart |multipart/form-data |`.formData` |
|
||||
|URL-Encoded Form|application/x-www-form-urlencoded|`.urlEncodedForm`|
|
||||
|Plaintext |text/plain |`.plainText` |
|
||||
|HTML |text/html |`.html` |
|
||||
|
||||
_Codable_ unterstützt leider nicht alle Medien.
|
||||
|
||||
## Binden der Zeichenfolge
|
||||
|
||||
```http
|
||||
GET /hello?name=Vapor HTTP/1.1
|
||||
content-length: 0
|
||||
```
|
||||
|
||||
Ähnlich wie beim Binden des Inhalts müssen wir für das Binden der Zeichenfolge eine Struktur anlegen und es mit dem Protokoll *Content* versehen.
|
||||
|
||||
Zusätzlich müssen wir die Eigenschaft *name* als optional deklarieren, da Parameter in einer Zeichenfolge immer optional sind.
|
||||
|
||||
```swift
|
||||
struct Hello: Content {
|
||||
var name: String?
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
app.get("hello") { req -> String in
|
||||
let hello = try req.query.decode(Hello.self)
|
||||
return "Hello, \(hello.name ?? "Anonymous")"
|
||||
}
|
||||
```
|
||||
|
||||
Zudem können wir auch Einzelwerte aus der Zeichenabfolge abrufen:
|
||||
|
||||
```swift
|
||||
app.get("hello") { req -> String in
|
||||
let name: String? = req.query["name"]
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
Vapor ruft automatisch jeweils die beiden Methoden _beforeEncode_ und _afterDecode_ eines Objektes von Typ _Content_ auf.
|
||||
|
||||
Die Methoden sind standardmäßig funktionslos, können aber im Bedarfsfall überschrieben werden.
|
||||
|
||||
```swift
|
||||
// Runs before this Content is encoded. `mutating` is only required for structs, not classes.
|
||||
mutating func beforeEncode() throws {
|
||||
|
||||
guard
|
||||
let name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!name.isEmpty
|
||||
else {
|
||||
throw Abort(.badRequest, reason: "Name must not be empty.")
|
||||
}
|
||||
self.name = name
|
||||
}
|
||||
|
||||
// Runs after this Content is decoded. `mutating` is only required for structs, not classes.
|
||||
mutating func afterDecode() throws {
|
||||
|
||||
self.name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let name = self.name, name.isEmpty {
|
||||
throw Abort(.badRequest, reason: "Name must not be empty.")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Standard überschreiben
|
||||
|
||||
Vapor's Standardkodierer kann global oder situationsabhängig überschrieben werden.
|
||||
|
||||
### Global
|
||||
|
||||
Für eine globale Verwendung eines eigenen Kodierer müssen wir ihn der _ContentConfiguration.global_ mitgeben.
|
||||
|
||||
```swift
|
||||
// create a new JSON encoder that uses unix-timestamp dates
|
||||
let encoder = JSONEncoder()
|
||||
encoder.dateEncodingStrategy = .secondsSince1970
|
||||
|
||||
// override the global encoder used for the `.json` media type
|
||||
ContentConfiguration.global.use(encoder: encoder, for: .json)
|
||||
```
|
||||
|
||||
### Situationsabhängig
|
||||
|
||||
Wir können aber auch den Bindungsmethoden abhängig von der Situation einen Kodierer mitgeben.
|
||||
|
||||
```swift
|
||||
// create a new JSON decoder that uses unix-timestamp dates
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .secondsSince1970
|
||||
|
||||
// decodes Hello struct using custom decoder
|
||||
let hello = try req.content.decode(Hello.self, using: decoder)
|
||||
```
|
||||
|
||||
## Benutzerdefinierte Kodierer
|
||||
|
||||
### Kodierer für Inhalt
|
||||
|
||||
Vapor hat die folgenden zwei Protokolle zum Binden von Inhalt vordefiniert.
|
||||
|
||||
```swift
|
||||
public protocol ContentEncoder {
|
||||
func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders) throws
|
||||
where E: Encodable
|
||||
}
|
||||
|
||||
public protocol ContentDecoder {
|
||||
func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders) throws -> D
|
||||
where D: Decodable
|
||||
}
|
||||
```
|
||||
|
||||
Indem wir einen unseren eigenen Kodierer mit diese beiden Protokolle versehen, kann er von _ContentConfiguration_ entgegengenommen werden.
|
||||
|
||||
### Kodierer für Zeichenfolge
|
||||
|
||||
Für das Binden einer Zeichenabfolge hat Vapor die folgenden zwei Protokolle vordefiniert.
|
||||
|
||||
```swift
|
||||
public protocol URLQueryDecoder {
|
||||
func decode<D>(_ decodable: D.Type, from url: URI) throws -> D
|
||||
where D: Decodable
|
||||
}
|
||||
|
||||
public protocol URLQueryEncoder {
|
||||
func encode<E>(_ encodable: E, to url: inout URI) throws
|
||||
where E: Encodable
|
||||
}
|
||||
```
|
||||
|
||||
### `ResponseEncodable`
|
||||
|
||||
Another approach involves implementing `ResponseEncodable` on your types. Consider this trivial `HTML` wrapper type:
|
||||
|
||||
```swift
|
||||
struct HTML {
|
||||
let value: String
|
||||
}
|
||||
```
|
||||
|
||||
Then its `ResponseEncodable` implementation would look like this:
|
||||
|
||||
```swift
|
||||
extension HTML: ResponseEncodable {
|
||||
public func encodeResponse(for request: Request) -> EventLoopFuture<Response> {
|
||||
var headers = HTTPHeaders()
|
||||
headers.add(name: .contentType, value: "text/html")
|
||||
return request.eventLoop.makeSucceededFuture(.init(
|
||||
status: .ok, headers: headers, body: .init(string: value)
|
||||
))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you're using `async`/`await` you can use `AsyncResponseEncodable`:
|
||||
|
||||
```swift
|
||||
extension HTML: AsyncResponseEncodable {
|
||||
public func encodeResponse(for request: Request) async throws -> Response {
|
||||
var headers = HTTPHeaders()
|
||||
headers.add(name: .contentType, value: "text/html")
|
||||
return .init(status: .ok, headers: headers, body: .init(string: value))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that this allows customizing the `Content-Type` header. See [`HTTPHeaders` reference](https://api.vapor.codes/vapor/documentation/vapor/response/headers) for more details.
|
||||
|
||||
You can then use `HTML` as a response type in your routes:
|
||||
|
||||
```swift
|
||||
app.get { _ in
|
||||
HTML(value: """
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, World!</h1>
|
||||
</body>
|
||||
</html>
|
||||
""")
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
# Content
|
||||
|
||||
La API de contenido de Vapor nos permite codificar / decodificar fácilmente estructuras Codable en / desde mensajes HTTP. La codificación [JSON](https://tools.ietf.org/html/rfc7159) se usa por defecto con soporte preparado para [Formulario URL-Encoded](https://en.wikipedia.org/wiki/Percent-encoding#The_application/x-www-form-urlencoded_type) y [Multipart](https://tools.ietf.org/html/rfc2388). La API también se puede configurar, permitiéndote agregar, modificar o reemplazar estrategias de codificación para ciertos tipos de contenido HTTP.
|
||||
|
||||
## Presentación
|
||||
|
||||
Para comprender cómo funciona la API de contenido de Vapor, primero debes comprender algunos conceptos básicos sobre los mensajes HTTP. Presta atención a la siguiente solicitud de ejemplo.
|
||||
|
||||
```http
|
||||
POST /greeting HTTP/1.1
|
||||
content-type: application/json
|
||||
content-length: 18
|
||||
|
||||
{"hello": "world"}
|
||||
```
|
||||
|
||||
Esta petición indica que contiene datos codificados en JSON utilizando la cabecera (header) `content-type` y el tipo de contenido (media type) `application/json`. A continuación, algunos datos JSON se hayan en el cuerpo (body) de la petición, después de las cabeceras.
|
||||
|
||||
### Estructura del Contenido
|
||||
|
||||
El primer paso para decodificar este mensaje HTTP es crear un tipo Codable que coincida con la estructura esperada.
|
||||
|
||||
```swift
|
||||
struct Greeting: Content {
|
||||
var hello: String
|
||||
}
|
||||
```
|
||||
|
||||
Conformar el tipo con `Content` agregará automáticamente la conformidad con `Codable`, junto con utilidades adicionales para trabajar con la API de contenido.
|
||||
|
||||
Una vez que tengas la estructura del contenido, puedes decodificarlo desde la solicitud entrante usando `req.content`.
|
||||
|
||||
```swift
|
||||
app.post("greeting") { req in
|
||||
let greeting = try req.content.decode(Greeting.self)
|
||||
print(greeting.hello) // "world"
|
||||
return HTTPStatus.ok
|
||||
}
|
||||
```
|
||||
|
||||
El método de decodificación `decode` utiliza el tipo de contenido de la solicitud para encontrar un decodificador apropiado. Si no se encuentra un decodificador, o la solicitud no contiene el header del tipo de contenido, se lanzará un error `415`.
|
||||
|
||||
Eso significa que esta ruta acepta automáticamente todos los demás tipos de contenido admitidos, como el formulario url-encoded:
|
||||
|
||||
```http
|
||||
POST /greeting HTTP/1.1
|
||||
content-type: application/x-www-form-urlencoded
|
||||
content-length: 11
|
||||
|
||||
hello=world
|
||||
```
|
||||
|
||||
En el caso de subidas de archivos, la propiedad de contenido debe ser del tipo `Data`
|
||||
|
||||
```swift
|
||||
struct Profile: Content {
|
||||
var name: String
|
||||
var email: String
|
||||
var image: Data
|
||||
}
|
||||
```
|
||||
|
||||
### Tipos de Contenido Soportados
|
||||
|
||||
A continuación se muestran los tipos de contenido (media types) que la API admite por defecto.
|
||||
|
||||
|nombre|valor de header|media type|
|
||||
|-|-|-|
|
||||
|JSON|application/json|`.json`|
|
||||
|Multipart|multipart/form-data|`.formData`|
|
||||
|URL-Encoded Form|application/x-www-form-urlencoded|`.urlEncodedForm`|
|
||||
|Plaintext|text/plain|`.plainText`|
|
||||
|HTML|text/html|`.html`|
|
||||
|
||||
No todos los tipos de contenido son compatibles con todas las funciones `Codable`. Por ejemplo, JSON no admite fragmentos de nivel superior (top-level) y Plaintext no admite datos anidados.
|
||||
|
||||
## Consultas (Query)
|
||||
|
||||
Las API de contenido de Vapor admiten el manejo de datos URL codificados en la cadena de consulta.
|
||||
|
||||
### Decodificación
|
||||
|
||||
Para comprender cómo funciona la decodificación de una cadena de consulta de URL, echa un vistazo a la siguiente solicitud de ejemplo.
|
||||
|
||||
```http
|
||||
GET /hello?name=Vapor HTTP/1.1
|
||||
content-length: 0
|
||||
```
|
||||
|
||||
Al igual que las APIs para manejar el contenido del body del mensaje HTTP, el primer paso para analizar cadenas de consulta de URL es crear un `struct` que coincida con la estructura esperada.
|
||||
|
||||
```swift
|
||||
struct Hello: Content {
|
||||
var name: String?
|
||||
}
|
||||
```
|
||||
|
||||
Ten en cuenta que `name` es una `String` opcional, ya que las cadenas de consulta de URL siempre deben ser opcionales. Si deseas solicitar un parámetro, utiliza un parámetro de ruta en su lugar.
|
||||
|
||||
Ahora que tienes un struct `Content` para la cadena de consulta esperada de esta ruta, puedes decodificarla.
|
||||
|
||||
```swift
|
||||
app.get("hello") { req -> String in
|
||||
let hello = try req.query.decode(Hello.self)
|
||||
return "Hello, \(hello.name ?? "Anonymous")"
|
||||
}
|
||||
```
|
||||
|
||||
Esta ruta daría como resultado la siguiente respuesta a la solicitud de ejemplo anterior:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 12
|
||||
|
||||
Hello, Vapor
|
||||
```
|
||||
|
||||
Si se omitiera la cadena de consulta, como en la siguiente solicitud, se usaría en su lugar el nombre "Anonymous".
|
||||
|
||||
```http
|
||||
GET /hello HTTP/1.1
|
||||
content-length: 0
|
||||
```
|
||||
|
||||
### Valores Simples
|
||||
|
||||
Además de decodificar a un struct `Content`, Vapor también soporta la obtención de valores individuales de la cadena de consulta mediante subíndices.
|
||||
|
||||
```swift
|
||||
let name: String? = req.query["name"]
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
Vapor llamará automáticamente a `beforeEncode` y `afterDecode` en un tipo `Content`. Se proporcionan implementaciones predeterminadas que no hacen nada, pero puedes usar estos métodos para ejecutar una lógica personalizada.
|
||||
|
||||
```swift
|
||||
// Se ejecuta después de decodificar este Content. `mutating` solo se requiere para structs, no para clases.
|
||||
mutating func afterDecode() throws {
|
||||
// Es posible que no se pase name, pero si lo hace, entonces no puede ser una cadena vacía.
|
||||
self.name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let name = self.name, name.isEmpty {
|
||||
throw Abort(.badRequest, reason: "Name must not be empty.")
|
||||
}
|
||||
}
|
||||
|
||||
// Se ejecuta antes de que se codifique este Content. `mutating` solo se requiere para structs, no para clases.
|
||||
mutating func beforeEncode() throws {
|
||||
// *Siempre* tiene que devolver un name, y no puede ser una cadena vacía.
|
||||
guard
|
||||
let name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!name.isEmpty
|
||||
else {
|
||||
throw Abort(.badRequest, reason: "Name must not be empty.")
|
||||
}
|
||||
self.name = name
|
||||
}
|
||||
```
|
||||
|
||||
## Sobreescribiendo Valores Predeterminados
|
||||
|
||||
Los codificadores y decodificadores predeterminados utilizados por las APIs de Content de Vapor se pueden configurar.
|
||||
|
||||
### Global
|
||||
|
||||
`ContentConfiguration.global` te permite cambiar los codificadores y decodificadores que usa Vapor por defecto. Esto es útil para cambiar la forma en que toda la aplicación analiza y serializa los datos.
|
||||
|
||||
```swift
|
||||
// crea un nuevo JSON encoder que use fechas de marca de tiempo de Unix
|
||||
let encoder = JSONEncoder()
|
||||
encoder.dateEncodingStrategy = .secondsSince1970
|
||||
|
||||
// sobreescriba el codificador global utilizado para el media type `.json`
|
||||
ContentConfiguration.global.use(encoder: encoder, for: .json)
|
||||
```
|
||||
|
||||
La mutación de `ContentConfiguration` generalmente se realiza en `configure.swift`.
|
||||
|
||||
### Usos Únicos (One-Off)
|
||||
|
||||
Las llamadas a métodos de codificación y decodificación como `req.content.decode` admiten el paso de codificadores personalizados para usos únicos.
|
||||
|
||||
```swift
|
||||
// crea un nuevo JSON decoder que use fechas de marca de tiempo de Unix
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .secondsSince1970
|
||||
|
||||
// decodifica el struct Hello usando un decodificador personalizado
|
||||
let hello = try req.content.decode(Hello.self, using: decoder)
|
||||
```
|
||||
|
||||
## Codificadores Personalizados
|
||||
|
||||
Las aplicaciones y paquetes de terceros pueden agregar soporte para tipos de contenido que Vapor no admite de forma predeterminada mediante la creación de codificadores personalizados.
|
||||
|
||||
### Content
|
||||
|
||||
Vapor especifica dos protocolos para codificadores capaces de manejar contenido en el body de mensajes HTTP: `ContentDecoder` y `ContentEncoder`.
|
||||
|
||||
```swift
|
||||
public protocol ContentEncoder {
|
||||
func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders) throws
|
||||
where E: Encodable
|
||||
}
|
||||
|
||||
public protocol ContentDecoder {
|
||||
func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders) throws -> D
|
||||
where D: Decodable
|
||||
}
|
||||
```
|
||||
|
||||
Conformar con estos protocolos permite que sus codificadores personalizados se registren en `ContentConfiguration` como se especificó anteriormente.
|
||||
|
||||
### URL Query
|
||||
|
||||
Vapor especifica dos protocolos para codificadores capaces de manejar contenido en cadenas de consulta de URL: `URLQueryDecoder` y `URLQueryEncoder`.
|
||||
|
||||
```swift
|
||||
public protocol URLQueryDecoder {
|
||||
func decode<D>(_ decodable: D.Type, from url: URI) throws -> D
|
||||
where D: Decodable
|
||||
}
|
||||
|
||||
public protocol URLQueryEncoder {
|
||||
func encode<E>(_ encodable: E, to url: inout URI) throws
|
||||
where E: Encodable
|
||||
}
|
||||
```
|
||||
|
||||
Conformar con estos protocolos permite que sus codificadores personalizados se registren en `ContentConfiguration` para manejar cadenas de consulta de URL usando los métodos `use(urlEncoder:)` y `use(urlDecoder:)`.
|
||||
|
||||
### `ResponseEncodable` Personalizado
|
||||
|
||||
Otro enfoque consiste en implementar `ResponseEncodable` en sus tipos. Considera este tipo de wrapper `HTML` trivial:
|
||||
|
||||
```swift
|
||||
struct HTML {
|
||||
let value: String
|
||||
}
|
||||
```
|
||||
|
||||
Luego su implementación con `ResponseEncodable` se vería así:
|
||||
|
||||
```swift
|
||||
extension HTML: ResponseEncodable {
|
||||
public func encodeResponse(for request: Request) -> EventLoopFuture<Response> {
|
||||
var headers = HTTPHeaders()
|
||||
headers.add(name: .contentType, value: "text/html")
|
||||
return request.eventLoop.makeSucceededFuture(.init(
|
||||
status: .ok, headers: headers, body: .init(string: value)
|
||||
))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Si estás usando `async`/`await`, puedes usar `AsyncResponseEncodable`:
|
||||
|
||||
```swift
|
||||
extension HTML: AsyncResponseEncodable {
|
||||
public func encodeResponse(for request: Request) async throws -> Response {
|
||||
var headers = HTTPHeaders()
|
||||
headers.add(name: .contentType, value: "text/html")
|
||||
return .init(status: .ok, headers: headers, body: .init(string: value))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ten en cuenta que esto permite personalizar el header `Content-Type`. Consulta la [referencia de `HTTPHeaders`](https://api.vapor.codes/vapor/documentation/vapor/response/headers) para obtener más detalles.
|
||||
|
||||
Luego puede usar `HTML` como tipo de respuesta en tus rutas:
|
||||
|
||||
```swift
|
||||
app.get { _ in
|
||||
HTML(value: """
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, World!</h1>
|
||||
</body>
|
||||
</html>
|
||||
""")
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
# Entorno
|
||||
|
||||
La API de entorno (Environment) de Vapor te ayuda a configurar tu app de manera dinámica. Por defecto, tu app usará el entorno `development`. Puedes definir otros entornos útiles como `production` o `staging` y cambiar la configuración de la app para cada caso. También puedes cargar variables desde el entorno del proceso o desde ficheros `.env` (dotenv) dependiendo de tus necesidades.
|
||||
|
||||
Para acceder al entorno actual, usa `app.environment`. Puedes hacer un switch de esta propiedad en `configure(_:)` para ejecutar distintas lógicas de configuración.
|
||||
|
||||
```swift
|
||||
switch app.environment {
|
||||
case .production:
|
||||
app.databases.use(....)
|
||||
default:
|
||||
app.databases.use(...)
|
||||
}
|
||||
```
|
||||
|
||||
## Cambiando de Entorno
|
||||
|
||||
Por defecto, tu app se ejecutará en el entorno `development`. Puedes cambiar esto pasando el argumento (flag) `--env` (`-e`) durante el arranque de la app.
|
||||
|
||||
```swift
|
||||
swift run App serve --env production
|
||||
```
|
||||
|
||||
Vapor incluye los siguientes entornos:
|
||||
|
||||
|nombre|abreviación|descripción|
|
||||
|-|-|-|
|
||||
|production|prod|Distribuido a tus usuarios.|
|
||||
|development|dev|Desarrollo local.|
|
||||
|testing|test|Para test unitario (unit testing).|
|
||||
|
||||
!!! info "Información"
|
||||
El entorno `production` usará el nivel de registro `notice` por defecto si no se especifica otro. El resto de entornos usarán `info` por defecto.
|
||||
|
||||
Puedes pasar tanto el nombre entero como la abreviación del nombre en el argumento `--env` (`-e`).
|
||||
|
||||
```swift
|
||||
swift run App serve -e prod
|
||||
```
|
||||
|
||||
## Variables de Proceso
|
||||
|
||||
`Environment` ofrece una API simple basada en cadenas (string) para acceder a las variables de entorno de los procesos.
|
||||
|
||||
```swift
|
||||
let foo = Environment.get("FOO")
|
||||
print(foo) // String?
|
||||
```
|
||||
|
||||
Además del método `get`, `Environment` ofrece una API de búsqueda dinámica de miembros (dynamic member lookup) mediante `process`.
|
||||
|
||||
```swift
|
||||
let foo = Environment.process.FOO
|
||||
print(foo) // String?
|
||||
```
|
||||
|
||||
Al ejecutar tu app en el terminal, puedes estableces variables de entorno usando `export`.
|
||||
|
||||
```sh
|
||||
export FOO=BAR
|
||||
swift run App serve
|
||||
```
|
||||
|
||||
Al ejecutar tu app en Xcode, puedes estableces variables de entorno editando el esquema (scheme) de la `App`.
|
||||
|
||||
## .env (dotenv)
|
||||
|
||||
Los fichero dotenv contienen una lista de pares clave-valor para ser cargados automáticamente en el entorno. Estos ficheros facilitan la configuración de variables de entorno sin necesidad de establecerlas manualmente.
|
||||
|
||||
Vapor buscará ficheros dotenv en el directorio de trabajo actual (current working directory). Si estás usando Xcode, asegúrate de configurar el directorio de trabajo (working directory) editando el esquema (scheme) de la `App`.
|
||||
|
||||
Asume que el siguiente fichero `.env` está en la carpeta raíz de tu projecto:
|
||||
|
||||
```sh
|
||||
FOO=BAR
|
||||
```
|
||||
|
||||
Cuando tu aplicación arranque, podrás acceder a los contenidos de este fichero como si fueran otras variables de entorno de proceso.
|
||||
|
||||
```swift
|
||||
let foo = Environment.get("FOO")
|
||||
print(foo) // String?
|
||||
```
|
||||
|
||||
!!! info "Información"
|
||||
Las variables especificadas en ficheros `.env` no sobrescribirán variables ya existentes en el entorno de proceso.
|
||||
|
||||
Junto con `.env`, Vapor también tratará de cargar un fichero dotenv para el entorno actual. Por ejemplo, en el entorno `development`, Vapor cargará `.env.development`. Cualquier valor en el fichero de entorno especificado tendrá prioridad frente al fichero `.env` general.
|
||||
|
||||
Un típico patrón a seguir en los proyectos es incluir un fichero `.env` como una plantilla con valores predeterminados. Los ficheros de entorno específico pueden ser ignorados con este patrón de `.gitignore`:
|
||||
|
||||
```gitignore
|
||||
.env.*
|
||||
```
|
||||
|
||||
Cuando el proyecto es clonado a otra computadora, el fichero `.env` de plantilla puede ser copiado, para después insertar los valores correctos.
|
||||
|
||||
```sh
|
||||
cp .env .env.development
|
||||
vim .env.development
|
||||
```
|
||||
|
||||
!!! warning "Aviso"
|
||||
Los ficheros dotenv con información sensible como contraseñas no deben añadirse en los commits del control de versiones.
|
||||
|
||||
Si estás teniendo dificultades en la carga de ficheros dotenv, prueba a habilitar el registro de depuración (debug logging) con `--log debug` para obtener más información.
|
||||
|
||||
## Entornos Personalizados
|
||||
|
||||
Para definir un nombre de entorno personalizado, realiza una extensión de `Environment`.
|
||||
|
||||
```swift
|
||||
extension Environment {
|
||||
static var staging: Environment {
|
||||
.custom(name: "staging")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El entorno de la aplicación es establecido normalmente en `entrypoint.swift` usando `Environment.detect()`.
|
||||
|
||||
```swift
|
||||
@main
|
||||
enum Entrypoint {
|
||||
static func main() async throws {
|
||||
var env = try Environment.detect()
|
||||
try LoggingSystem.bootstrap(from: &env)
|
||||
|
||||
let app = Application(env)
|
||||
defer { app.shutdown() }
|
||||
|
||||
try await configure(app)
|
||||
try await app.runFromAsyncMainEntrypoint()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El método `detect` usa los argumentos de línea de comandos del proceso y analiza el argumento (flag) `--env` automáticamente. Puedes sobrescribir este comportamiento inicializando un struct `Environment` personalizado.
|
||||
|
||||
```swift
|
||||
let env = Environment(name: "testing", arguments: ["vapor"])
|
||||
```
|
||||
|
||||
El array de argumentos debe contener al menos uno que represente el nombre del ejecutable. Se pueden suministrar más argumentos para simular el paso de argumentos mediante línea de comandos. Esto es especialmente útil para testing.
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# Logging (Registro)
|
||||
|
||||
La API de logging (registro) de Vapor está hecha sobre [SwiftLog](https://github.com/apple/swift-log). Esto implica que Vapor es compatible con todas las [implementaciones de backend](https://github.com/apple/swift-log#backends) de SwiftLog.
|
||||
|
||||
## Logger
|
||||
|
||||
Las instancias de `Logger` son usadas para emitir mensajes de registro. Vapor proporciona varias formas sencillas de acceso a un logger (registrador).
|
||||
|
||||
### Petición (Request)
|
||||
|
||||
Cada `Request` entrante tiene un logger único que debes usar para cualquier registro específico de la propia petición.
|
||||
|
||||
```swift
|
||||
app.get("hello") { req -> String in
|
||||
req.logger.info("Hello, logs!")
|
||||
return "Hello, world!"
|
||||
}
|
||||
```
|
||||
|
||||
El logger de la petición incluye un UUID único que identifica la petición entrante para facilitar el seguimiento de los registros.
|
||||
|
||||
```
|
||||
[ INFO ] Hello, logs! [request-id: C637065A-8CB0-4502-91DC-9B8615C5D315] (App/routes.swift:10)
|
||||
```
|
||||
|
||||
!!! info "Información"
|
||||
Los metadatos del logger solo se mostrarán en el nivel de registro de depuración (debug) o inferiores.
|
||||
|
||||
### Aplicación
|
||||
|
||||
Para los mensajes de registro durante el arranque y la configuración de la app, usa el logger de `Application`.
|
||||
|
||||
```swift
|
||||
app.logger.info("Setting up migrations...")
|
||||
app.migrations.use(...)
|
||||
```
|
||||
|
||||
### Logger Personalizado
|
||||
|
||||
En situaciones donde no tengas acceso a `Application` o `Request`, puedes inicializar un nuevo `Logger`.
|
||||
|
||||
```swift
|
||||
let logger = Logger(label: "dev.logger.my")
|
||||
logger.info(...)
|
||||
```
|
||||
|
||||
Si bien los loggers personalizados emitirán registros al backend de registro que tengas configurado, los registros no contendrán metadatos adicionales relevantes, como el UUID de la petición. Utiliza los loggers específicos de las petición o de la aplicación siempre que puedas.
|
||||
|
||||
## Nivel
|
||||
|
||||
SwiftLog soporta una variedad de niveles de registro.
|
||||
|
||||
|nombre|descripción|
|
||||
|-|-|
|
||||
|trace|Apropiado para mensajes que contengan información usada normalmente cuando se está trazando la ejecución de un programa.|
|
||||
|debug|Apropiado para mensajes que contengan información usada normalmente en procesos de debug de un programa.|
|
||||
|info|Apropiado para mensajes informativos.|
|
||||
|notice|Apropiado para condiciones que no sean errores pero puedan requerir un manejo especial.|
|
||||
|warning|Apropiado para mensajes que no sean condiciones de error pero más severos que un aviso.|
|
||||
|error|Apropiado para condiciones de error.|
|
||||
|critical|Apropiado para el manejo de condiciones críticas de error que requieran atención inmediata.|
|
||||
|
||||
Cuando un mensaje `critical` es registrado, el backend de registro es libre de realizar operaciones más pesadas para capturar el estado del sistema, como la captura de rastros de pila (stack traces), para facilitar el proceso de depuración (debugging).
|
||||
|
||||
Por defecto, Vapor usará en nivel de registro `info`. Cuando sea ejecutado en el entorno `production`, se usará `notice` para mejorar el rendimiento.
|
||||
|
||||
### Cambiando el nivel de registro
|
||||
|
||||
Puedes cambiar el nivel de registro independientemente del modo de entorno para aumentar o disminuir la cantidad de registros producidos.
|
||||
|
||||
El primer método consiste en pasar la flag opcional `--log` cuando arranques tu aplicación.
|
||||
|
||||
```sh
|
||||
swift run App serve --log debug
|
||||
```
|
||||
|
||||
El segundo método consiste en establecer la variable de entorno `LOG_LEVEL`.
|
||||
|
||||
```sh
|
||||
export LOG_LEVEL=debug
|
||||
swift run App serve
|
||||
```
|
||||
|
||||
Ambos métodos pueden realizarse en Xcode editando el `App` scheme (esquema de la app).
|
||||
|
||||
## Configuración
|
||||
|
||||
SwiftLog se configura arrancando (bootstrapping) el `LoggingSystem` una vez por cada proceso. Los projectos de Vapor normalmente hacen esto en `entrypoint.swift`.
|
||||
|
||||
```swift
|
||||
var env = try Environment.detect()
|
||||
try LoggingSystem.bootstrap(from: &env)
|
||||
```
|
||||
|
||||
`bootstrap(from:)` es un método de ayuda (helper method) proporcionado por Vapor que configurará el handler de registros predeterminado en base a argumentos de línea de comandos y variables de entorno. El handler de registros predeterminado soporta la emisión de mensajes a la terminal con soporte de color ANSI.
|
||||
|
||||
### Handler Personalizado
|
||||
|
||||
Puedes sobrescribir el handler de registros predeterminado de Vapor y registrar el tuyo propio.
|
||||
|
||||
```swift
|
||||
import Logging
|
||||
|
||||
LoggingSystem.bootstrap { label in
|
||||
StreamLogHandler.standardOutput(label: label)
|
||||
}
|
||||
```
|
||||
|
||||
Todos los backend soportados por SwiftLog funcionarán con Vapor. Sin embargo, cambiar el nivel de registro con argumentos de línea de comandos y variables de entorno es compatible únicamente con el handler de registros predeterminado de Vapor.
|
||||
|
|
@ -0,0 +1,436 @@
|
|||
# Routing
|
||||
|
||||
El enrutamiento (o routing) consiste en encontrar el controlador apropiado para una petición entrante. El núcleo del routing de Vapor lo compone el router de tres nodos de alto rendimiento de [RoutingKit](https://github.com/vapor/routing-kit).
|
||||
|
||||
## Presentación
|
||||
|
||||
Para comprender cómo funciona routing en Vapor, primero debes comprender algunos conceptos básicos sobre las peticiones HTTP (requests). Eche un vistazo a la siguiente petición de ejemplo.
|
||||
|
||||
```http
|
||||
GET /hello/vapor HTTP/1.1
|
||||
host: vapor.codes
|
||||
content-length: 0
|
||||
```
|
||||
|
||||
Esta es una simple petición HTTP `GET` a la URL `/hello/vapor`. Este es el tipo de petición HTTP que haría tu navegador si apuntara a la siguiente URL.
|
||||
|
||||
```
|
||||
http://vapor.codes/hello/vapor
|
||||
```
|
||||
|
||||
### Métodos HTTP
|
||||
|
||||
La primera parte de la petición es el método de HTTP. `GET` es el método más común de HTTP, pero hay varios que usarás con frecuencia. Estos métodos HTTP a menudo se asocian con la semántica [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete).
|
||||
|
||||
|Método|CRUD|
|
||||
|-|-|
|
||||
|`GET`|Leer|
|
||||
|`POST`|Crear|
|
||||
|`PUT`|Reemplazar|
|
||||
|`PATCH`|Actualizar|
|
||||
|`DELETE`|Borrar|
|
||||
|
||||
### Ruta de Petición (Path)
|
||||
|
||||
Justo después del método HTTP está la URI (identificador de recursos uniforme) de la petición. Consiste en una ruta que comienza en `/` y una cadena de consulta opcional detrás de `?`. El método HTTP y la ruta son los que usa Vapor para enrutar las peticiones.
|
||||
|
||||
Después de la URI está la versión HTTP seguida de cero o más encabezados (headers) y finalmente el cuerpo de la respuesta (body). Dado que se trata de una petición `GET`, no hay cuerpo.
|
||||
|
||||
### Métodos de Router
|
||||
|
||||
Echemos un vistazo a cómo se podría manejar esta petición en Vapor.
|
||||
|
||||
```swift
|
||||
app.get("hello", "vapor") { req in
|
||||
return "Hello, vapor!"
|
||||
}
|
||||
```
|
||||
|
||||
Todos los métodos comunes de HTTP están disponibles como métodos en `Application`. Aceptan uno o más argumentos de cadena que representan la ruta de la petición separados por `/`.
|
||||
|
||||
Ten en cuenta que también podrías escribirlo usando `on` seguido del método.
|
||||
|
||||
```swift
|
||||
app.on(.GET, "hello", "vapor") { ... }
|
||||
```
|
||||
|
||||
Con esta ruta registrada, la petición HTTP de ejemplo anterior dará como resultado la siguiente respuesta HTTP.
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 13
|
||||
content-type: text/plain; charset=utf-8
|
||||
|
||||
Hello, vapor!
|
||||
```
|
||||
|
||||
### Parámetros de Ruta
|
||||
|
||||
Ahora que hemos creado una petición de ruta con éxito basada en el método y ruta HTTP, intentemos hacer que la ruta sea dinámica. Ten en cuenta que el nombre "vapor" está codificado tanto en la ruta como en la respuesta. Hagámosla dinámica para que puedas visitar `/hello/<cualquier nombre>` y obtener una respuesta.
|
||||
|
||||
```swift
|
||||
app.get("hello", ":name") { req -> String in
|
||||
let name = req.parameters.get("name")!
|
||||
return "Hello, \(name)!"
|
||||
}
|
||||
```
|
||||
|
||||
Al usar un componente de ruta con el prefijo `:`, le indicamos al router que se trata de un componente dinámico. Cualquier cadena suministrada aquí ahora coincidirá con esta ruta. Luego podemos usar `req.parameters` para acceder al valor de la cadena.
|
||||
|
||||
Si vuelves a ejecutar la petición de ejemplo, seguirás recibiendo una respuesta que saluda a vapor. Sin embargo, ahora puedes incluir cualquier nombre luego de `/hello/` y verlo incluido en la respuesta. Probemos `/hello/swift`.
|
||||
|
||||
```http
|
||||
GET /hello/swift HTTP/1.1
|
||||
content-length: 0
|
||||
```
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 13
|
||||
content-type: text/plain; charset=utf-8
|
||||
|
||||
Hello, swift!
|
||||
```
|
||||
|
||||
Ahora que comprendemos los conceptos básicos, consultemos cada sección para obtener más información sobre parámetros, grupos y más.
|
||||
|
||||
## Rutas
|
||||
|
||||
Una ruta especifica un controlador de peticiones para un método HTTP y una ruta URI determinados. También puede almacenar metadatos adicionales.
|
||||
|
||||
### Métodos
|
||||
|
||||
Las rutas se pueden registrar directamente en tu `Application` utilizando varios métodos auxiliares de HTTP.
|
||||
|
||||
```swift
|
||||
// responde a GET /foo/bar/baz
|
||||
app.get("foo", "bar", "baz") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Las estructuras de ruta admiten el retorno de cualquier cosa que sea `ResponseEncodable`. Esto incluye `Content`, un closure `async`, y cualquier `EventLoopFuture` donde su valor future sea `ResponseEncodable`.
|
||||
|
||||
Puedes especificar el tipo de retorno de una ruta usando `-> T` antes de `in`. Esto puede ser útil en situaciones en las que el compilador no puede determinar el tipo de retorno.
|
||||
|
||||
```swift
|
||||
app.get("foo") { req -> String in
|
||||
return "bar"
|
||||
}
|
||||
```
|
||||
|
||||
Estos son los métodos de ruta auxiliares soportados:
|
||||
|
||||
- `get`
|
||||
- `post`
|
||||
- `patch`
|
||||
- `put`
|
||||
- `delete`
|
||||
|
||||
Además de los métodos auxiliares de HTTP, existe una función `on` que acepta el método HTTP como un parámetro de entrada.
|
||||
|
||||
```swift
|
||||
// responde a OPTIONS /foo/bar/baz
|
||||
app.on(.OPTIONS, "foo", "bar", "baz") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Componente de Ruta
|
||||
|
||||
Cada método de registro de ruta acepta una lista variádica de `PathComponent`. Este tipo es expresable por un string literal y consta de cuatro casos:
|
||||
|
||||
- Constante (`foo`)
|
||||
- Parámetro (`:foo`)
|
||||
- Cualquier cosa (`*`)
|
||||
- Comodín (`**`)
|
||||
|
||||
#### Constante
|
||||
|
||||
Este es un componente de ruta estático. Solo permitirá peticiones con una cadena que coincida exactamente en valor y posición con la especificada en la ruta.
|
||||
|
||||
```swift
|
||||
// responde a GET /foo/bar/baz
|
||||
app.get("foo", "bar", "baz") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Parámetro
|
||||
|
||||
Este es un componente de ruta dinámico. Se permitirá cualquier cadena en esta posición. Un componente de ruta de parámetro se especifica con el prefijo `:`. La cadena que sigue a `:` será el nombre del parámetro, el cual podrás usar para obtener su valor en la petición.
|
||||
|
||||
```swift
|
||||
// responde a GET /foo/bar/baz
|
||||
// responde a GET /foo/qux/baz
|
||||
// ...
|
||||
app.get("foo", ":bar", "baz") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Cualquier cosa
|
||||
|
||||
Este es muy similar a parámetro, excepto que el valor se descarta. Este componente de ruta es especificado simplemente con un `*`.
|
||||
|
||||
```swift
|
||||
// responde a GET /foo/bar/baz
|
||||
// responde a GET /foo/qux/baz
|
||||
// ...
|
||||
app.get("foo", "*", "baz") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Comodín
|
||||
|
||||
Este es un componente de ruta dinámico que coincide con uno o más componentes. Se especifica usando simplemente `**`. Cualquier cadena en esta posición o en posiciones posteriores coincidirá con la petición.
|
||||
|
||||
```swift
|
||||
// responde a GET /foo/bar
|
||||
// responde a GET /foo/bar/baz
|
||||
// ...
|
||||
app.get("foo", "**") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Parámetros
|
||||
|
||||
Cuando se utiliza un componente de ruta de parámetro (con el prefijo `:`), el valor de la URI en esa posición se almacenará en `req.parameters`. Puedes utilizar el nombre del componente de ruta para acceder al valor.
|
||||
|
||||
```swift
|
||||
// responde a GET /hello/foo
|
||||
// responde a GET /hello/bar
|
||||
// ...
|
||||
app.get("hello", ":name") { req -> String in
|
||||
let name = req.parameters.get("name")!
|
||||
return "Hello, \(name)!"
|
||||
}
|
||||
```
|
||||
|
||||
!!! tip "Consejo"
|
||||
Podemos estar seguros de que `req.parameters.get` nunca devolverá `nil` ya que nuestra ruta incluye `:name`. Sin embargo, si se está accediendo a parámetros de ruta en un middleware o en código activado por múltiples rutas, deberías manejar la posibilidad de un `nil`.
|
||||
|
||||
!!! tip "Consejo"
|
||||
Si deseas recuperar parámetros de consulta de URL (por ejemplo `/hello/?name=foo`), necesitas usar la API Content de Vapor para manejar los datos codificados del string de la URL. Consulta la referencia de [`Content`](content.md) para más detalles.
|
||||
|
||||
`req.parameters.get` también soporta convertir el parámetro a los tipos `LosslessStringConvertible` automáticamente.
|
||||
|
||||
```swift
|
||||
// responde a GET /number/42
|
||||
// responde a GET /number/1337
|
||||
// ...
|
||||
app.get("number", ":x") { req -> String in
|
||||
guard let int = req.parameters.get("x", as: Int.self) else {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
return "\(int) is a great number"
|
||||
}
|
||||
```
|
||||
|
||||
Los valores de URI que coincidan con un Comodín (`**`) se guardarán en `req.parameters` como `[String]`. Puedes usar `req.parameters.getCatchall` para acceder a estos componentes.
|
||||
|
||||
```swift
|
||||
// responde a GET /hello/foo
|
||||
// responde a GET /hello/foo/bar
|
||||
// ...
|
||||
app.get("hello", "**") { req -> String in
|
||||
let name = req.parameters.getCatchall().joined(separator: " ")
|
||||
return "Hello, \(name)!"
|
||||
}
|
||||
```
|
||||
|
||||
### Transmisión de Body
|
||||
|
||||
Al registrar una ruta utilizando el método `on`, puedes especificar cómo se debe manejar la petición de Body (el cuerpo de la respuesta). Por defecto, los cuerpos de las peticiones se recopilan en memoria antes de llamar a su controlador correspondiente. Esto es útil ya que permite que la decodificación del contenido sea síncrona aunque su aplicación lea las peticiones entrantes de forma asíncrona.
|
||||
|
||||
Por defecto. Vapor limitará la recopilación de la transmisión del Body a un tamaño de 16KB. Puedes configurar esto usando `app.routes`.
|
||||
|
||||
```swift
|
||||
// Aumenta el límite de recopilación de la transmisión de Body hasta 500kb
|
||||
app.routes.defaultMaxBodySize = "500kb"
|
||||
```
|
||||
|
||||
Si una transmisión de Body que se está recopilando excede el límite configurado, se lanzará un error `413 Payload Too Large`.
|
||||
|
||||
Para configurar la estrategia de recopilación de Body en una ruta individual, usa el parámetro `body`.
|
||||
|
||||
```swift
|
||||
// Recopila la transmisión de Body (hasta 1mb de tamaño) antes de llamar a esta ruta.
|
||||
app.on(.POST, "listings", body: .collect(maxSize: "1mb")) { req in
|
||||
// Administra la petición.
|
||||
}
|
||||
```
|
||||
|
||||
Si se proporciona el `maxSize` (tamaño máximo) al realizar el `collect` (la recopilación), se sobrescribirá el valor predeterminado de la aplicación para esa ruta. Para utilizar el valor por defecto de la aplicación, omita el argumento `maxSize`.
|
||||
|
||||
Para peticiones grandes, como subida de archivos, recopilar el Body en un búfer puede sobrecargar la memoria del sistema. Para evitar que se recopile el Body de la petición, usa la estrategia `stream`.
|
||||
|
||||
```swift
|
||||
// El cuerpo de la petición no se recopilará en un búfer.
|
||||
app.on(.POST, "upload", body: .stream) { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Cuando se transmite el cuerpo de la petición, `req.body.data` será `nil`. Debes usar `req.body.drain` para manejar cada fragmento a medida que se envía a tu ruta.
|
||||
|
||||
### Rutas Case Insensitive
|
||||
|
||||
El comportamiento por defecto para las rutas distingue (case-sensitive) y preserva (case-preserving) entre mayúsculas y minúsculas. Los componentes de ruta constantes (`Constant`) pueden manejarse alternativamente sin distinguir ni preservar entre mayúsculas y minúsculas a efectos de cada ruta; para habilitar este comportamiento, configura lo siguiente antes del inicio de la aplicación:
|
||||
|
||||
```swift
|
||||
app.routes.caseInsensitive = true
|
||||
```
|
||||
|
||||
No se realizarán cambios en la petición de origen; las rutas recibirán los componentes sin ninguna modificación.
|
||||
|
||||
### Visualizando Rutas
|
||||
|
||||
Puedes acceder a las rutas de tu aplicación creando el servicio `Routes` o utilizando `app.routes`.
|
||||
|
||||
```swift
|
||||
print(app.routes.all) // [Route]
|
||||
```
|
||||
|
||||
Vapor también posee el comando `routes` que imprime todas las rutas disponibles en una tabla con formato ASCII.
|
||||
|
||||
```sh
|
||||
$ swift run App routes
|
||||
+--------+----------------+
|
||||
| GET | / |
|
||||
+--------+----------------+
|
||||
| GET | /hello |
|
||||
+--------+----------------+
|
||||
| GET | /todos |
|
||||
+--------+----------------+
|
||||
| POST | /todos |
|
||||
+--------+----------------+
|
||||
| DELETE | /todos/:todoID |
|
||||
+--------+----------------+
|
||||
```
|
||||
|
||||
### Metadata
|
||||
|
||||
Todos los registro de métodos de rutas retornan la ruta creada (`Route`). Esto te permite agregar metadatos al diccionario `userInfo` de la ruta. Hay algunos métodos predeterminados disponibles, como agregar una descripción a la ruta.
|
||||
|
||||
```swift
|
||||
app.get("hello", ":name") { req in
|
||||
...
|
||||
}.description("says hello")
|
||||
```
|
||||
|
||||
## Grupos de Ruta
|
||||
|
||||
La agrupación de rutas permite crear un conjunto de rutas con un prefijo o un middleware específico. La agrupación admite una sintaxis basada en builders y closures.
|
||||
|
||||
Todos los métodos de agrupación devuelven un `RouteBuilder`, permitiendo mezclar, combinar y anidar infinitamente sus grupos con otros métodos de creación de rutas.
|
||||
|
||||
### Prefijo de Ruta
|
||||
|
||||
Los grupos de rutas con prefijo permiten anteponer uno o más componentes de ruta a un grupo de rutas.
|
||||
|
||||
```swift
|
||||
let users = app.grouped("users")
|
||||
// GET /users
|
||||
users.get { req in
|
||||
...
|
||||
}
|
||||
// POST /users
|
||||
users.post { req in
|
||||
...
|
||||
}
|
||||
// GET /users/:id
|
||||
users.get(":id") { req in
|
||||
let id = req.parameters.get("id")!
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Cualquier componente de ruta que puedas pasar como `get` o `post` puede pasarse dentro de un `grouped`. También hay una sintaxis alternativa basada en closures.
|
||||
|
||||
```swift
|
||||
app.group("users") { users in
|
||||
// GET /users
|
||||
users.get { req in
|
||||
...
|
||||
}
|
||||
// POST /users
|
||||
users.post { req in
|
||||
...
|
||||
}
|
||||
// GET /users/:id
|
||||
users.get(":id") { req in
|
||||
let id = req.parameters.get("id")!
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La anidación de grupos de rutas con prefijos te permite definir de manera concisa las APIs de un CRUD.
|
||||
|
||||
```swift
|
||||
app.group("users") { users in
|
||||
// GET /users
|
||||
users.get { ... }
|
||||
// POST /users
|
||||
users.post { ... }
|
||||
|
||||
users.group(":id") { user in
|
||||
// GET /users/:id
|
||||
user.get { ... }
|
||||
// PATCH /users/:id
|
||||
user.patch { ... }
|
||||
// PUT /users/:id
|
||||
user.put { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
Además de prefijar componentes de rutas, también puedes agregar un middleware a grupos de rutas.
|
||||
|
||||
```swift
|
||||
app.get("fast-thing") { req in
|
||||
...
|
||||
}
|
||||
app.group(RateLimitMiddleware(requestsPerMinute: 5)) { rateLimited in
|
||||
rateLimited.get("slow-thing") { req in
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Esto es especialmente útil para proteger subconjuntos de rutas con diferentes middleware de autenticación.
|
||||
|
||||
```swift
|
||||
app.post("login") { ... }
|
||||
let auth = app.grouped(AuthMiddleware())
|
||||
auth.get("dashboard") { ... }
|
||||
auth.get("logout") { ... }
|
||||
```
|
||||
|
||||
## Redirecciones
|
||||
|
||||
Los redireccionamientos (redirect) son útiles en varios escenarios, como reenviar ubicaciones antiguas a nuevas para el SEO, redireccionar a un usuario no autenticado a la página de inicio de sesión o mantener la compatibilidad con versiones anteriores de su API.
|
||||
|
||||
Para redirigir una petición, utiliza:
|
||||
|
||||
```swift
|
||||
req.redirect(to: "/some/new/path")
|
||||
```
|
||||
|
||||
También puedes especificar el tipo de redirección, por ejemplo para redirigir una página de forma permanente (para que su SEO se actualice correctamente) usa:
|
||||
|
||||
```swift
|
||||
req.redirect(to: "/some/new/path", type: .permanent)
|
||||
```
|
||||
|
||||
Los diferentes `RedirectType` son:
|
||||
|
||||
* `.permanent` - devuelve una redirección **301 Moved Permanently**
|
||||
* `.normal` - devuelve una redirección **303 See Other**. Este es el valor por defecto de Vapor y le dice al cliente que siga la redirección con una petición **GET**.
|
||||
* `.temporary` - devuelve una redirección **307 Temporary Redirect**. Esto le dice al cliente que conserve el método HTTP utilizado en la petición.
|
||||
|
||||
> Para elegir el código de estado de redirección adecuado, consulte [la lista completa](https://es.wikipedia.org/wiki/Anexo:Códigos_de_estado_HTTP#3xx:_Redirecciones)
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
# Validación
|
||||
|
||||
La API Validation de Vapor te ayuda a validar peticiones entrantes antes de usar la API [Content](content.md) para decodificar datos.
|
||||
|
||||
## Introducción
|
||||
|
||||
La profunda integración del protocolo de tipado seguro `Codable` de Swift en Vapor hace que no tengas que preocuparte tanto por la validación de datos como lo harías en un lenguaje de tipado dinámico. Sin embargo, existen varias razones por las que puedas querer validación explícita usando la API Validation.
|
||||
|
||||
### Errores legibles por humanos
|
||||
|
||||
Decodificar struct usando la API [Content](content.md) dará errores si alguno de los datos no es válido. Sin embargo, a veces estos mensajes de error pueden carecer de legibilidad para un humano. Por ejemplo, teniendo el siguiente enum respaldado por cadenas:
|
||||
|
||||
```swift
|
||||
enum Color: String, Codable {
|
||||
case red, blue, green
|
||||
}
|
||||
```
|
||||
|
||||
Si un usuario trata de pasar la cadena `"purple"` a una propiedad de tipo `Color`, recibirán un error similar al siguiente:
|
||||
|
||||
```
|
||||
Cannot initialize Color from invalid String value purple for key favoriteColor
|
||||
```
|
||||
|
||||
Aunque este error es técnicamente correcto y ha protegido con éxito el endpoint ante un valor no válido, podría informar de una mejor forma al usuario acerca del error, indicándole las opciones disponibles. Usando la API de validación (Validation), puedes generar errores como el siguiente:
|
||||
|
||||
```
|
||||
favoriteColor is not red, blue, or green
|
||||
```
|
||||
|
||||
Es más, `Codable` interrumpirá la decodificación de un tipo en cuanto reciba el primer error. Esto implica que, aunque varias de las propiedades de la petición sean inválidas, el usuario solo verá el primer error. La API Validation informará sobre todos los errores de validación en una única petición.
|
||||
|
||||
### Validación específica
|
||||
|
||||
`Codable` controla bien la validación de tipos, pero a veces puedes querer más que eso. Por ejemplo, validar el contenido de una cadena o el tamaño de un número entero. La API Validation posee validadores para ayudar en la validación de datos como emails, conjuntos de caracteres, rangos de números enteros y más.
|
||||
|
||||
## Validatable
|
||||
|
||||
Para validar una petición, deberás generar una colección de `Validations`. La manera más común de hacerlo es conformar un tipo existente a `Validatable`.
|
||||
|
||||
Echemos un vistazo a cómo podrías añadir validación a este simple endpoint de `POST /users`. Esta guía asume que ya estás familiarizado con la API [Content](content.md).
|
||||
|
||||
```swift
|
||||
enum Color: String, Codable {
|
||||
case red, blue, green
|
||||
}
|
||||
|
||||
struct CreateUser: Content {
|
||||
var name: String
|
||||
var username: String
|
||||
var age: Int
|
||||
var email: String
|
||||
var favoriteColor: Color?
|
||||
}
|
||||
|
||||
app.post("users") { req -> CreateUser in
|
||||
let user = try req.content.decode(CreateUser.self)
|
||||
// Haz algo con user.
|
||||
return user
|
||||
}
|
||||
```
|
||||
|
||||
### Añadiendo Validaciones
|
||||
|
||||
El primer paso es conformar el tipo que estás decodificando, en este caso `CreateUser`, a `Validatable`. Esto puede hacerse en una extensión.
|
||||
|
||||
```swift
|
||||
extension CreateUser: Validatable {
|
||||
static func validations(_ validations: inout Validations) {
|
||||
// Aquí van las validaciones.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El método estático `validations(_:)` se llamará cuando `CreateUser` sea validado. Cualquier validación que quieras llevar a cabo deberá ser añadida a la colección `Validations` proporcionada. Veamos como añadir una simple validación que requiera que el email del usuario sea válido.
|
||||
|
||||
```swift
|
||||
validations.add("email", as: String.self, is: .email)
|
||||
```
|
||||
|
||||
El primer parámetro es la clave esperada del valor, en este caso, `"email"`. Esta clave debería ser igual al nombre de la propiedad del tipo que se está validando. El segundo parámetro, `as`, es el tipo esperado, en este caso, `String`. Suele coincidir con el tipo de la propiedad, pero no siempre. Finalmente, pueden añadirse uno o más validadores después en el tercer parámetro, `is`. En este caso estamos añadiendo un único validador que comprueba si el valor es una dirección de email.
|
||||
|
||||
### Validando contenido de peticiones
|
||||
|
||||
Una vez hayas conformado tu tipo a `Validatable`, la función estática `validate(content:)` puede usarse para validar el contenido de una petición. Antes de `req.content.decode(CreateUser.self)`, añade la línea a continuación en el controlador de ruta:
|
||||
|
||||
```swift
|
||||
try CreateUser.validate(content: req)
|
||||
```
|
||||
|
||||
Ahora, prueba a enviar la siguiente petición con un email no válido:
|
||||
|
||||
```http
|
||||
POST /users HTTP/1.1
|
||||
Content-Length: 67
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"age": 4,
|
||||
"email": "foo",
|
||||
"favoriteColor": "green",
|
||||
"name": "Foo",
|
||||
"username": "foo"
|
||||
}
|
||||
```
|
||||
|
||||
Deberías recibir el siguiente error:
|
||||
|
||||
```
|
||||
email is not a valid email address
|
||||
```
|
||||
|
||||
### Validando la consulta (query) de una petición
|
||||
|
||||
Los tipos conformados con `Validatable` también disponen de `validate(query:)`, que puede usarse para validar la cadena de consulta (query string) de una petición. Añade las siguientes líneas al controlador de ruta:
|
||||
|
||||
```swift
|
||||
try CreateUser.validate(query: req)
|
||||
req.query.decode(CreateUser.self)
|
||||
```
|
||||
|
||||
Ahora, prueba a enviar la siguiente petición con un email no válido en la cadena de consulta.
|
||||
|
||||
```http
|
||||
GET /users?age=4&email=foo&favoriteColor=green&name=Foo&username=foo HTTP/1.1
|
||||
|
||||
```
|
||||
|
||||
Deberías recibir el siguiente error:
|
||||
|
||||
```
|
||||
email is not a valid email address
|
||||
```
|
||||
|
||||
### Validación de números enteros
|
||||
|
||||
Genial, ahora probemos a añadir una validación para `age`.
|
||||
|
||||
```swift
|
||||
validations.add("age", as: Int.self, is: .range(13...))
|
||||
```
|
||||
|
||||
La validación de edad requiere que `age` sea igual o superior a `13`. Si pruebas la petición mencionada arriba, deberías ver un nuevo error:
|
||||
|
||||
```
|
||||
age is less than minimum of 13, email is not a valid email address
|
||||
```
|
||||
|
||||
### Validación de cadenas (string)
|
||||
|
||||
A continuación, añadiremos validaciones para `name` y `username`.
|
||||
|
||||
```swift
|
||||
validations.add("name", as: String.self, is: !.empty)
|
||||
validations.add("username", as: String.self, is: .count(3...) && .alphanumeric)
|
||||
```
|
||||
|
||||
La validación de `name` usa el operador `!` para invertir la validación de `.empty`, requiriendo que la cadena no sea vacía.
|
||||
|
||||
La validación de `username` combina dos validadores mediante el uso de `&&`, requiriendo que la cadena tenga una longitud de, al menos, 3 caracteres _y_ que contenga únicamente caracteres alfanuméricos.
|
||||
|
||||
### Validación de Enums
|
||||
|
||||
Finalmente, veremos una validación ligeramente más avanzada para comprobar que el `favoriteColor` proporcionado es válido.
|
||||
|
||||
```swift
|
||||
validations.add(
|
||||
"favoriteColor", as: String.self,
|
||||
is: .in("red", "blue", "green"),
|
||||
required: false
|
||||
)
|
||||
```
|
||||
|
||||
Como no es posible decodificar un `Color` desde un valor no válido, esta validación usa `String` como tipo base. Utiliza en validador `.in` para verificar que el valor es una opción válida: red, blue o green. Como este valor es opcional, `required` se establece a `false` para indicar que la validación no debería fallar si esta clave no se encuentra en los datos de la petición.
|
||||
|
||||
Ten en cuenta que, aunque la validación de `favoriteColor` pasará si la clave falta, no pasará si se proporciona `null` (nulo). Si quieres soportar `null`, cambia el tipo de la validación a `String?` y usa el operador de conveniencia `.nil ||` (leer como: "es nil (nulo) o ...").
|
||||
|
||||
```swift
|
||||
validations.add(
|
||||
"favoriteColor", as: String?.self,
|
||||
is: .nil || .in("red", "blue", "green"),
|
||||
required: false
|
||||
)
|
||||
```
|
||||
|
||||
### Errores Personalizados
|
||||
|
||||
Puede que quieras añadir errores personalizados que sean legibles por humanos a tus `Validations` o `Validator`. Para hacerlo, simplemente proporciona el parámetro adicional `customFailureDescription`, que sobrescribirá el error por defecto.
|
||||
|
||||
```swift
|
||||
validations.add(
|
||||
"name",
|
||||
as: String.self,
|
||||
is: !.empty,
|
||||
customFailureDescription: "Provided name is empty!"
|
||||
)
|
||||
validations.add(
|
||||
"username",
|
||||
as: String.self,
|
||||
is: .count(3...) && .alphanumeric,
|
||||
customFailureDescription: "Provided username is invalid!"
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
## Validadores (Validators)
|
||||
|
||||
Debajo tienes una lista de los validadores soportados actualmente y una breve explicación de lo que hacen.
|
||||
|
||||
|Validación|Descripción|
|
||||
|-|-|
|
||||
|`.ascii`|Contiene únicamente caracteres ASCII.|
|
||||
|`.alphanumeric`|Contiene únicamente caracteres alfanuméricos.|
|
||||
|`.characterSet(_:)`|Contiene únicamente caracteres del `CharacterSet` (conjunto de caracteres) proporcionado.|
|
||||
|`.count(_:)`|El número de elementos (count) de una colección está entre los límites proporcionados.|
|
||||
|`.email`|Contiene un email válido.|
|
||||
|`.empty`|La colección está vacía.|
|
||||
|`.in(_:)`|El valor se encuentra en la `Collection` (colección) proporcionada.|
|
||||
|`.nil`|El valor es `null` (nulo).|
|
||||
|`.range(_:)`|El valor se encuentra en el `Range` (rango) proporcionado.|
|
||||
|`.url`|Contiene una URL válida.|
|
||||
|
||||
Los validadores también pueden combinarse mediante operadores para construir validaciones complejas.
|
||||
|
||||
|Operador|Posición|Descripción|
|
||||
|-|-|-|
|
||||
|`!`|prefijo|Invierte un validador, requiriendo lo opuesto.|
|
||||
|`&&`|infijo|Combina dos validadores, requiere ambos.|
|
||||
|`||`|infijo|Combina dos validadores, requiere al menos uno.|
|
||||
|
|
@ -13,7 +13,7 @@ Make sure that you've installed the heroku cli tool.
|
|||
### HomeBrew
|
||||
|
||||
```bash
|
||||
brew install heroku/brew/heroku
|
||||
brew tap heroku/brew && brew install heroku
|
||||
```
|
||||
|
||||
### Other Install Options
|
||||
|
|
@ -52,7 +52,7 @@ git init
|
|||
|
||||
#### Master
|
||||
|
||||
By default, Heroku deploys the **master** branch. Make sure all changes are checked into this branch before pushing.
|
||||
You should decide for one branch and stick to that for deploying to Heroku, like the **main** or **master** branch. Make sure all changes are checked into this branch before pushing.
|
||||
|
||||
Check your current branch with
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ git branch
|
|||
The asterisk indicates current branch.
|
||||
|
||||
```bash
|
||||
* master
|
||||
* main
|
||||
commander
|
||||
other-branches
|
||||
```
|
||||
|
|
@ -72,10 +72,10 @@ The asterisk indicates current branch.
|
|||
If you don’t see any output and you’ve just performed `git init`. You’ll need to commit your code first then you’ll see output from the `git branch` command.
|
||||
|
||||
|
||||
If you’re _not_ currently on **master**, switch there by entering:
|
||||
If you’re _not_ currently on the right branch, switch there by entering (for **main**):
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git checkout main
|
||||
```
|
||||
|
||||
#### Commit changes
|
||||
|
|
@ -111,13 +111,13 @@ heroku buildpacks:set vapor/vapor
|
|||
|
||||
### Swift version file
|
||||
|
||||
The buildpack we added looks for a **.swift-version** file to know which version of swift to use. (replace 5.2.1 with whatever version your project requires.)
|
||||
The buildpack we added looks for a **.swift-version** file to know which version of swift to use. (replace 5.8.1 with whatever version your project requires.)
|
||||
|
||||
```bash
|
||||
echo "5.2.1" > .swift-version
|
||||
echo "5.8.1" > .swift-version
|
||||
```
|
||||
|
||||
This creates **.swift-version** with `5.2.1` as its contents.
|
||||
This creates **.swift-version** with `5.8.1` as its contents.
|
||||
|
||||
|
||||
### Procfile
|
||||
|
|
@ -151,12 +151,12 @@ git commit -m "adding heroku build files"
|
|||
You're ready to deploy, run this from the terminal. It may take a while to build, this is normal.
|
||||
|
||||
```none
|
||||
git push heroku master
|
||||
git push heroku main
|
||||
```
|
||||
|
||||
### Scale Up
|
||||
|
||||
Once you've built successfully, you need to add at least one server, one web is free and you can get it with the following:
|
||||
Once you've built successfully, you need to add at least one server. Prices start at $5/month for the Eco plan (see [pricing](https://www.heroku.com/pricing#containers)), make sure you have payment configured on Heroku. Then for a single web worker:
|
||||
|
||||
```bash
|
||||
heroku ps:scale web=1
|
||||
|
|
@ -164,7 +164,7 @@ heroku ps:scale web=1
|
|||
|
||||
### Continued Deployment
|
||||
|
||||
Any time you want to update, just get the latest changes into master and push to heroku and it will redeploy
|
||||
Any time you want to update, just get the latest changes into main and push to heroku and it will redeploy
|
||||
|
||||
## Postgres
|
||||
|
||||
|
|
@ -172,9 +172,9 @@ Any time you want to update, just get the latest changes into master and push to
|
|||
|
||||
Visit your application at dashboard.heroku.com and go to the **Add-ons** section.
|
||||
|
||||
From here enter `postgress` and you'll see an option for `Heroku Postgres`. Select it.
|
||||
From here enter `postgres` and you'll see an option for `Heroku Postgres`. Select it.
|
||||
|
||||
Choose the hobby dev free plan, and provision. Heroku will do the rest.
|
||||
Choose the Eco plan for $5/month (see [pricing](https://www.heroku.com/pricing#data-services)), and provision. Heroku will do the rest.
|
||||
|
||||
Once you finish, you’ll see the database appears under the **Resources** tab.
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ Each Vapor app on your server should have its own configuration file. For an exa
|
|||
|
||||
```sh
|
||||
[program:hello]
|
||||
command=/home/vapor/hello/.build/release/Run serve --env production
|
||||
command=/home/vapor/hello/.build/release/App serve --env production
|
||||
directory=/home/vapor/hello/
|
||||
user=vapor
|
||||
stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ Elke Vapor toepassing op uw server zou zijn eigen configuratiebestand moeten heb
|
|||
|
||||
```sh
|
||||
[program:hello]
|
||||
command=/home/vapor/hello/.build/release/Run serve --env production
|
||||
command=/home/vapor/hello/.build/release/App serve --env production
|
||||
directory=/home/vapor/hello/
|
||||
user=vapor
|
||||
stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ sudo dnf install supervisor
|
|||
|
||||
```sh
|
||||
[program:hello]
|
||||
command=/home/vapor/hello/.build/release/Run serve --env production
|
||||
command=/home/vapor/hello/.build/release/App serve --env production
|
||||
directory=/home/vapor/hello/
|
||||
user=vapor
|
||||
stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Group=vapor
|
|||
Restart=always
|
||||
RestartSec=3
|
||||
WorkingDirectory=/home/vapor/hello
|
||||
ExecStart=/home/vapor/hello/.build/release/Run serve --env production
|
||||
ExecStart=/home/vapor/hello/.build/release/App serve --env production
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=vapor-hello
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Group=vapor
|
|||
Restart=always
|
||||
RestartSec=3
|
||||
WorkingDirectory=/home/vapor/hello
|
||||
ExecStart=/home/vapor/hello/.build/release/Run serve --env production
|
||||
ExecStart=/home/vapor/hello/.build/release/App serve --env production
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=vapor-hello
|
||||
|
|
|
|||
|
|
@ -0,0 +1,591 @@
|
|||
# Fluent
|
||||
|
||||
Fluent es un framework [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) para Swift. Aprovecha el sólido sistema de tipado de Swift para proporcionar una interfaz fácil de usar para el manejo de bases de datos. El uso de Fluent se centra en la creación de tipos de modelo que representan estructuras de datos en la base de datos. Estos modelos se utilizan para realizar operaciones de creación, lectura, actualización y eliminación en lugar de escribir consultas directas a la base de datos.
|
||||
|
||||
## Configuración
|
||||
|
||||
Al crear un nuevo proyecto, habiendo utilizando el comando `vapor new`, responder "sí" para incluir Fluent y elegir qué controlador de base de datos se va a utilizar. Esto agregará automáticamente las dependencias al nuevo proyecto, así como código de ejemplo para realizar la configuración.
|
||||
|
||||
### Proyecto Existente
|
||||
|
||||
Si ya se dispone de un proyecto al que se quiere agregar Fluent, se deberán de agregar dos dependencias al paquete [package](../getting-started/spm.md):
|
||||
|
||||
- [vapor/fluent](https://github.com/vapor/fluent)@4.0.0
|
||||
- Uno (o más) controladores de Fluent que se deseen seleccionar
|
||||
|
||||
```swift
|
||||
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
|
||||
.package(url: "https://github.com/vapor/fluent-<db>-driver.git", from: <version>),
|
||||
```
|
||||
|
||||
```swift
|
||||
.target(name: "App", dependencies: [
|
||||
.product(name: "Fluent", package: "fluent"),
|
||||
.product(name: "Fluent<db>Driver", package: "fluent-<db>-driver"),
|
||||
.product(name: "Vapor", package: "vapor"),
|
||||
]),
|
||||
```
|
||||
|
||||
Una vez agregados los paquetes como dependencias, las bases de datos se configuran usando `app.databases` en `configure.swift`.
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
import Fluent<db>Driver
|
||||
|
||||
app.databases.use(<db config>, as: <identifier>)
|
||||
```
|
||||
|
||||
A continuación, se detallan uno por uno y de forma más específica los detalles de configuración de cada uno de los controladores de Fluent.
|
||||
|
||||
### Controladores
|
||||
|
||||
Fluent actualmente tiene cuatro controladores oficialmente compatibles. Si se desea obtener una lista completa, tanto de controladores oficiales como de terceros de base de datos Fluent, se debe buscar en GitHub la etiqueta [`fluent-driver`](https://github.com/topics/fluent-driver).
|
||||
|
||||
#### PostgreSQL
|
||||
|
||||
PostgreSQL es una base de datos de código abierto que cumple con los estándares SQL. Es fácilmente configurable en la mayoría de los proveedores de alojamiento en la nube. Este es el controlador de base de datos **recommendado** para Fluent.
|
||||
|
||||
Para usar PostgreSQL, agregar las siguientes dependencias al paquete.
|
||||
|
||||
```swift
|
||||
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0")
|
||||
```
|
||||
|
||||
```swift
|
||||
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver")
|
||||
```
|
||||
|
||||
Una vez agregadas las dependencias, configura la base de datos con Fluent utilizando `app.databases.use` en `configure.swift`.
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
import FluentPostgresDriver
|
||||
|
||||
app.databases.use(.postgres(hostname: "localhost", username: "vapor", password: "vapor", database: "vapor"), as: .psql)
|
||||
```
|
||||
|
||||
También se pueden expecificar las credenciales mediante una cadena de texto que defina la conexión a la base de datos.
|
||||
|
||||
```swift
|
||||
try app.databases.use(.postgres(url: "<connection string>"), as: .psql)
|
||||
```
|
||||
|
||||
#### SQLite
|
||||
|
||||
SQLite es una base de datos SQL integrada de código abierto. Su naturaleza simplista lo convierte en una excelente opción para prototipos y pruebas.
|
||||
|
||||
Para usar SQLite, se deben de agregar las siguientes dependencias al paquete.
|
||||
|
||||
```swift
|
||||
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0")
|
||||
```
|
||||
|
||||
```swift
|
||||
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver")
|
||||
```
|
||||
|
||||
Una vez agregadas las dependencias, configura la base de datos con Fluent utilizando `app.databases.use` en `configure.swift`.
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
import FluentSQLiteDriver
|
||||
|
||||
app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
|
||||
```
|
||||
|
||||
También es posible configurar SQLite para que el almacenamiento de la base de datos tan solo se realice en memoria.
|
||||
|
||||
```swift
|
||||
app.databases.use(.sqlite(.memory), as: .sqlite)
|
||||
```
|
||||
|
||||
Si se utiliza una base de datos en memoria, asegurar que la configuración de Fluent se realiza mediante la migración automática usando `--auto-migrate` o ejecutando `app.autoMigrate()` después de agregar las migraciones.
|
||||
|
||||
```swift
|
||||
app.migrations.add(CreateTodo())
|
||||
try app.autoMigrate().wait()
|
||||
// or
|
||||
try await app.autoMigrate()
|
||||
```
|
||||
|
||||
!!! tip "Consejo"
|
||||
La configuración de SQLite habilita automáticamente las restricciones de clave foranea en todas las conexiones creadas, pero no modifica las configuraciones de clave foranea en la base de datos en sí. La eliminación directa de registros en la base de datos podría violar las restricciones y desencadenar errores de clave foranea.
|
||||
|
||||
#### MySQL
|
||||
|
||||
MySQL es una popular base de datos SQL de código abierto. Está disponible en muchos proveedores de alojamiento en la nube. Añadir que este controlador también es compatible con MariaDB.
|
||||
|
||||
Para usar MySQL, se deben de agregar las siguientes dependencias al paquete.
|
||||
|
||||
```swift
|
||||
.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0")
|
||||
```
|
||||
|
||||
```swift
|
||||
.product(name: "FluentMySQLDriver", package: "fluent-mysql-driver")
|
||||
```
|
||||
|
||||
Una vez que se hayan agregado las dependencias, configurar la base de datos con Fluent utilizando `app.databases.use` en `configure.swift`.
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
import FluentMySQLDriver
|
||||
|
||||
app.databases.use(.mysql(hostname: "localhost", username: "vapor", password: "vapor", database: "vapor"), as: .mysql)
|
||||
```
|
||||
|
||||
También se pueden expecificar las credenciales mediante una cadena de texto que defina la conexión a la base de datos.
|
||||
|
||||
```swift
|
||||
try app.databases.use(.mysql(url: "<connection string>"), as: .mysql)
|
||||
```
|
||||
|
||||
Para configurar una conexión local sin un certificado SSL, se debe deshabilitar la verificación del certificado. Por ejemplo, es posible que esto sea necesario si se está conectando a una base de datos MySQL 8 mediante Docker.
|
||||
|
||||
```swift
|
||||
var tls = TLSConfiguration.makeClientConfiguration()
|
||||
tls.certificateVerification = .none
|
||||
|
||||
app.databases.use(.mysql(
|
||||
hostname: "localhost",
|
||||
username: "vapor",
|
||||
password: "vapor",
|
||||
database: "vapor",
|
||||
tlsConfiguration: tls
|
||||
), as: .mysql)
|
||||
```
|
||||
|
||||
!!! warning "Advertencia"
|
||||
Nunca deshabilitar la verificación del certificado en producción. Se deberá de proporcionar un certificado a la configuración de `TLSConfiguration` para su verificación.
|
||||
|
||||
#### MongoDB
|
||||
|
||||
MongoDB es una base de datos popular NoSQL y sin esquemas diseñada para los programadores. El controlador es compatible con todos los proveedores de alojamiento en la nube y con las instalaciones en un hospedaje propio a partir de la versión 3.4 y en adelante.
|
||||
|
||||
!!! note "Nota"
|
||||
Este controlador está impulsado por un cliente de MongoDB creado y mantenido por la comunidad llamado [MongoKitten](https://github.com/OpenKitten/MongoKitten). MongoDB mantiene un cliente oficial, [mongo-swift-driver](https://github.com/mongodb/mongo-swift-driver), junto con una integración de Vapor, mongodb-vapor.
|
||||
|
||||
Para usar MongoDB, se deben de agregar las siguientes dependencias al paquete.
|
||||
|
||||
```swift
|
||||
.package(url: "https://github.com/vapor/fluent-mongo-driver.git", from: "1.0.0"),
|
||||
```
|
||||
|
||||
```swift
|
||||
.product(name: "FluentMongoDriver", package: "fluent-mongo-driver")
|
||||
```
|
||||
|
||||
Una vez que se hayan agregado las dependencias, configurar la base de datos con Fluent utilizando `app.databases.use` en `configure.swift`.
|
||||
|
||||
Para conectarse, se debe de usar una cadena de texto el formato de [conexión estándar URI](https://docs.mongodb.com/master/reference/connection-string/index.html) de MongoDB.
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
import FluentMongoDriver
|
||||
|
||||
try app.databases.use(.mongo(connectionString: "<connection string>"), as: .mongo)
|
||||
```
|
||||
|
||||
## Modelos
|
||||
|
||||
Los modelos representan estructuras de datos fijas en la base de datos, como tablas o colecciones. Los modelos tienen uno o más campos que almacenan valores codificables. Todos los modelos también tienen un identificador único. Los Property Wrappers se utilizan para denotar identificadores y campos, así como mapeos más complejos mencionados posteriormente. El modelo de ejemplo a continuación representa una galaxia.
|
||||
|
||||
```swift
|
||||
final class Galaxy: Model {
|
||||
// Nombre de la tabla o colección.
|
||||
static let schema = "galaxies"
|
||||
|
||||
// Identificador único de esta Galaxia.
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
|
||||
// El nombre de la galaxia.
|
||||
@Field(key: "name")
|
||||
var name: String
|
||||
|
||||
// Crea una nueva Galaxia vacía.
|
||||
init() { }
|
||||
|
||||
// Crea una nueva Galaxia con todas las propiedades establecidas.
|
||||
init(id: UUID? = nil, name: String) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Para crear un nuevo modelo, crea una nueva clase que conforme a `Model`.
|
||||
|
||||
!!! tip "Consejo"
|
||||
Se recomienda marcar las clases de modelos como `final` para mejorar el rendimiento y simplificar los requisitos de conformidad.
|
||||
|
||||
El primer requisito del protocolo `Model` es la cadena estática `schema`.
|
||||
|
||||
```swift
|
||||
static let schema = "galaxies"
|
||||
```
|
||||
|
||||
Esta propiedad le indica a Fluent a qué tabla o colección corresponde el modelo. Esto puede ser una tabla que ya existe en la base de datos o una que creará con una [migración](#migrations). El esquema suele ser `snake_case` y en plural.
|
||||
|
||||
### Identificador
|
||||
|
||||
El siguiente requisito es un campo de identificador llamado `id`.
|
||||
|
||||
```swift
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
```
|
||||
|
||||
Este campo debe usar el property wrapper `@ID`. Fluent recomienda usar `UUID` y el campo especial `.id` ya que esto es compatible con todos los controladores de Fluent.
|
||||
|
||||
Si se desea utilizar una clave o tipo de ID personalizado, se debe de usar la sobrecarga de [`@ID(custom:)`](model.md#custom-identifier).
|
||||
|
||||
### Campos
|
||||
|
||||
Después de agregar el identificador, se pueden agregar tantos campos como se deseen para almacenar información adicional. En este ejemplo, el único campo adicional es el nombre de la galaxia.
|
||||
|
||||
```swift
|
||||
@Field(key: "name")
|
||||
var name: String
|
||||
```
|
||||
|
||||
Para campos simples, se utiliza el property wrapper `@Field`. Al igual que con `@ID`, el parámetro `key` especifica el nombre del campo en la base de datos. Esto es especialmente útil en casos en los que la convención de nomenclatura de campos de la base de datos puede ser diferente a la de Swift, por ejemplo, usando `snake_case` en lugar de `camelCase`.
|
||||
|
||||
A continuación, todos los modelos requieren un init vacío. Esto permite que Fluent cree nuevas instancias del modelo.
|
||||
|
||||
```swift
|
||||
init() { }
|
||||
```
|
||||
|
||||
Por último, se puede agregar un inicializador de conveniencia para que se puedan establecer todas las propiedades en el modelo.
|
||||
|
||||
```swift
|
||||
init(id: UUID? = nil, name: String) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
}
|
||||
```
|
||||
|
||||
El uso de inicializadores de conveniencia es especialmente útil si se agregan nuevas propiedades al modelo, ya que se pueden obtener errores en tiempo de compilación si se cambia el método de inicialización.
|
||||
|
||||
## Migraciones
|
||||
|
||||
Si las base de datos utiliza esquemas predefinidos, como las bases de datos SQL, se necesita una migración para preparar la base de datos para el modelo. Las migraciones también son útiles para poblar las bases de datos con datos. Para crear una migración, define un nuevo tipo que se ajuste al protocolo `Migration` o `AsyncMigration`. Esta sería la siguiente migración para el modelo de Galaxia previamente definido.
|
||||
|
||||
```swift
|
||||
struct CreateGalaxy: AsyncMigration {
|
||||
// Prepara la base de datos para almacenar modelos de Galaxia.
|
||||
func prepare(on database: Database) async throws {
|
||||
try await database.schema("galaxies")
|
||||
.id()
|
||||
.field("name", .string)
|
||||
.create()
|
||||
}
|
||||
|
||||
// Opcionalmente revierte los cambios realizados en el método prepare.
|
||||
func revert(on database: Database) async throws {
|
||||
try await database.schema("galaxies").delete()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El método `prepare` se utiliza para preparar la base de datos para almacenar modelos de `Galaxy`.
|
||||
|
||||
### Esquema
|
||||
|
||||
En este método, se utiliza `database.schema(_:)` para crear un nuevo `SchemaBuilder`. Luego se agregan uno o más `field`s al constructor antes de llamar a `create()` para crear el esquema.
|
||||
|
||||
Cada campo agregado al constructor tiene un nombre, un tipo y restricciones opcionales.
|
||||
|
||||
```swift
|
||||
field(<name>, <type>, <optional constraints>)
|
||||
```
|
||||
|
||||
Hay un método `id()` de conveniencia para agregar propiedades `@ID` utilizando los valores predeterminados recomendados de Fluent.
|
||||
|
||||
El `revert` en la migración deshace cualquier cambio realizado en el método `prepare`. En este caso, eso significa eliminar el esquema de Galaxia.
|
||||
|
||||
Una vez que se define la migración, se debe de informar a Fluent sobre ella agregándola a `app.migrations` en `configure.swift`.
|
||||
|
||||
```swift
|
||||
app.migrations.add(CreateGalaxy())
|
||||
```
|
||||
|
||||
### Migrar
|
||||
|
||||
Para realizar migraciones, ejecuta `swift run App migrate` desde la línea de comandos o agrega `migrate` como argumento al esquema de la aplicación en Xcode.
|
||||
|
||||
```
|
||||
$ swift run App migrate
|
||||
Migrate Command: Prepare
|
||||
The following migration(s) will be prepared:
|
||||
+ CreateGalaxy on default
|
||||
Would you like to continue?
|
||||
y/n> y
|
||||
Migration successful
|
||||
```
|
||||
|
||||
## Consultando
|
||||
|
||||
Ahora que el modelo ha sido creado exitosamente y migrado a la base de datos, es el momento de hacer la primera consulta.
|
||||
|
||||
### All
|
||||
|
||||
La siguiente ruta devolverá una colección de todas las galaxias en la base de datos.
|
||||
|
||||
```swift
|
||||
app.get("galaxies") { req async throws in
|
||||
try await Galaxy.query(on: req.db).all()
|
||||
}
|
||||
```
|
||||
|
||||
Para que la Galaxia pueda ser devuelta en un ruta de consulta es necesario agregar la conformidad con `Content`.
|
||||
|
||||
```swift
|
||||
final class Galaxy: Model, Content {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
`Galaxy.query` se utiliza para crear un nuevo generador de consultas para el modelo. `req.db` es una referencia a la base de datos predeterminada de la aplicación. Por último, `all()` devuelve todos los modelos almacenados en la base de datos.
|
||||
|
||||
Si tras compilar y ejecutar el proyecto, se realiza una solicitud de tipo `GET /galaxies`, se devolverá una colección vacía. Por lo tanto, se tendrá que agregar una ruta para crear una nueva galaxia.
|
||||
|
||||
### Creación
|
||||
|
||||
Siguiendo la convención RESTful, se utilizará el punto de entrada `POST /galaxies` para crear una nueva galaxia. Dado que los modelos son decodificables (codables), se puede decodificar una galaxia directamente desde el cuerpo de la solicitud.
|
||||
|
||||
```swift
|
||||
app.post("galaxies") { req -> EventLoopFuture<Galaxy> in
|
||||
let galaxy = try req.content.decode(Galaxy.self)
|
||||
return galaxy.create(on: req.db)
|
||||
.map { galaxy }
|
||||
}
|
||||
```
|
||||
|
||||
!!! seealso "Ver también"
|
||||
Consulta [Content → Overview](../basics/content.md) para obtener más información sobre la decodificación de los cuerpos de las solicitudes.
|
||||
|
||||
Una vez se tiene una instancia del modelo, llamar a `create(on:)` guarda el modelo en la base de datos. Esto devuelve un `EventLoopFuture<Void>` que indica que el guardado se ha completado. Una vez que se completa el guardado, devuelve el modelo recién creado utilizando `map`.
|
||||
|
||||
Si se está utilizando `async`/`await`, el código se puede escribir de la siguiente manera:
|
||||
|
||||
```swift
|
||||
app.post("galaxies") { req async throws -> Galaxy in
|
||||
let galaxy = try req.content.decode(Galaxy.self)
|
||||
try await galaxy.create(on: req.db)
|
||||
return galaxy
|
||||
}
|
||||
```
|
||||
|
||||
En este caso, la operación async no tiene valor de retorno. La instancia se devolverá una vez que se complete el guardado.
|
||||
|
||||
Compilar, ejecutar el proyecto y envíar la siguiente solicitud.
|
||||
|
||||
```http
|
||||
POST /galaxies HTTP/1.1
|
||||
content-length: 21
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"name": "Milky Way"
|
||||
}
|
||||
```
|
||||
|
||||
Devolverá el modelo creado con un identificador como respuesta.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": ...,
|
||||
"name": "Milky Way"
|
||||
}
|
||||
```
|
||||
|
||||
Ahora, si se consulta otra vez `GET /galaxies`, se debería de ver la galaxia recién creada devuelta en la colección.
|
||||
|
||||
## Relaciones
|
||||
|
||||
¡Las galaxias no son nada sin estrellas! Echemos un vistazo rápido a las potentes características de relaciones de Fluent mediante la adición de una relación uno a muchos entre `Galaxy` y un nuevo modelo `Star`.
|
||||
|
||||
```swift
|
||||
final class Star: Model, Content {
|
||||
// Nombre de la tabla o colección.
|
||||
static let schema = "stars"
|
||||
|
||||
// Identificador único para esta Estrella.
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
|
||||
// Nombre de la Estrella.
|
||||
@Field(key: "name")
|
||||
var name: String
|
||||
|
||||
// Referencia a la Galaxia en la que se encuentra esta Estrella.
|
||||
@Parent(key: "galaxy_id")
|
||||
var galaxy: Galaxy
|
||||
|
||||
// Crea una nueva Estrella vacía.
|
||||
init() { }
|
||||
|
||||
// Crea una nueva Estrella con todas las propiedades establecidas.
|
||||
init(id: UUID? = nil, name: String, galaxyID: UUID) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.$galaxy.id = galaxyID
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Padre
|
||||
|
||||
El nuevo modelo de `Star` es muy similar a la `Galaxy`, excepto por un nuevo tipo de campo: `@Parent`.
|
||||
|
||||
```swift
|
||||
@Parent(key: "galaxy_id")
|
||||
var galaxy: Galaxy
|
||||
```
|
||||
|
||||
La propiedad `parent` es un campo que almacena el identificador de otro modelo. El modelo que contiene la referencia se llama "hijo" y el modelo referenciado se llama "padre". Este tipo de relación también se conoce como "uno a muchos". El parámetro clave de la propiedad especifica el nombre del campo que se debe usar para almacenar la clave del padre en la base de datos.
|
||||
|
||||
En el método init, se establece el identificador del padre utilizando `$galaxy`.
|
||||
|
||||
```swift
|
||||
self.$galaxy.id = galaxyID
|
||||
```
|
||||
|
||||
Al anteponer el nombre de la propiedad padre con `$`, se accede al envoltorio de propiedad subyacente. Esto es necesario para acceder al `@Field` interno que almacena el valor real del identificador.
|
||||
|
||||
!!! seealso "Ver También"
|
||||
Consultar la propuesta de Evolución de Swift sobre envoltorios de propiedad, para obtener más información: [[SE-0258] Property Wrappers](https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md)
|
||||
|
||||
A continuación, crear una migración para preparar la base de datos para manejar las `Star`.
|
||||
|
||||
```swift
|
||||
struct CreateStar: AsyncMigration {
|
||||
// Prepara la base de datos para almacenar los modelos de Estrella.
|
||||
func prepare(on database: Database) async throws {
|
||||
try await database.schema("stars")
|
||||
.id()
|
||||
.field("name", .string)
|
||||
.field("galaxy_id", .uuid, .references("galaxies", "id"))
|
||||
.create()
|
||||
}
|
||||
|
||||
// Opcionalmente revierte los cambios realizados en el método prepare.
|
||||
func revert(on database: Database) async throws {
|
||||
try await database.schema("stars").delete()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Esto es casi igual a la migración de la Galaxia, excepto por el campo adicional para almacenar el identificador de la galaxia padre.
|
||||
|
||||
```swift
|
||||
field("galaxy_id", .uuid, .references("galaxies", "id"))
|
||||
```
|
||||
|
||||
Este campo especifica una restricción opcional que indica a la base de datos que el valor del campo hace referencia al campo "id" en el esquema "galaxies". Esto también se conoce como una clave externa y ayuda a garantizar la integridad de los datos.
|
||||
|
||||
Una vez que se crea la migración, hay que añadirlo a las migraciones de `app.migrations` después de la migración `CreateGalaxy`.
|
||||
|
||||
```swift
|
||||
app.migrations.add(CreateGalaxy())
|
||||
app.migrations.add(CreateStar())
|
||||
```
|
||||
|
||||
Dado que las migraciones se ejecutan en orden y `CreateStar` hace referencia al esquema "galaxies", el orden es importante. Por último, [ejecuta las migraciones](#migrate) para preparar la base de datos.
|
||||
|
||||
Agregar una ruta para crear nuevas estrellas.
|
||||
|
||||
```swift
|
||||
app.post("stars") { req async throws -> Star in
|
||||
let star = try req.content.decode(Star.self)
|
||||
try await star.create(on: req.db)
|
||||
return star
|
||||
}
|
||||
```
|
||||
|
||||
Crear una nueva estrella haciendo referencia a la galaxia previamente creada utilizando la siguiente petición HTTP.
|
||||
|
||||
```http
|
||||
POST /stars HTTP/1.1
|
||||
content-length: 36
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"name": "Sun",
|
||||
"galaxy": {
|
||||
"id": ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Debería devolver la creación de la nueva estrella con un identificador único.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": ...,
|
||||
"name": "Sun",
|
||||
"galaxy": {
|
||||
"id": ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Hijos
|
||||
|
||||
Ahora veamos cómo puedes utilizar la función de carga anticipada de Fluent para devolver automáticamente las estrellas de una galaxia en la ruta `GET /galaxies`. Agregar la siguiente propiedad al modelo `Galaxy`.
|
||||
|
||||
```swift
|
||||
// Todas las estrellas en esta galaxia.
|
||||
@Children(for: \.$galaxy)
|
||||
var stars: [Star]
|
||||
```
|
||||
|
||||
El envoltorio de propiedad `@Children` es el inverso de `@Parent`. Toma un camino de clave al campo `@Parent` del hijo como argumento para el parámetro `for`. Su valor es una matriz de hijos, ya que puede existir cero o más modelos hijo. No se necesitan cambios en la migración de la galaxia, ya que toda la información necesaria para esta relación se almacena en `Star`.
|
||||
|
||||
### Carga forzada
|
||||
|
||||
Ahora que la relación está completa, se puede usar el método `with` en el generador de consultas para buscar y serializar automáticamente la relación entre galaxia y estrella.
|
||||
|
||||
```swift
|
||||
app.get("galaxies") { req in
|
||||
try await Galaxy.query(on: req.db).with(\.$stars).all()
|
||||
}
|
||||
```
|
||||
|
||||
Se pasa un `key-path` `@Children` a `with` para indicarle a Fluent que cargue automáticamente esta relación en todos los modelos resultantes. Compilar y ejecutar la aplicación y envíar otra solicitud a `GET /galaxies`. Ahora se debería de ver que las estrellas se incluyen automáticamente en la respuesta.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": ...,
|
||||
"name": "Milky Way",
|
||||
"stars": [
|
||||
{
|
||||
"id": ...,
|
||||
"name": "Sun",
|
||||
"galaxy": {
|
||||
"id": ...
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Registro de consultas
|
||||
|
||||
Los controladores de Fluent registran el SQL generado en el nivel de registro de depuración. Algunos controladores, como FluentPostgreSQL, permiten configurar esto al configurar la base de datos.
|
||||
|
||||
Para establecer el nivel de registro, en **configure.swift** (o donde se esté configurando la aplicación), agregar:
|
||||
|
||||
```swift
|
||||
app.logger.logLevel = .debug
|
||||
```
|
||||
|
||||
Esto le indica a la aplicación que el nivel de registro es **depuración**. Para que los cambios se apliquen, requiere que se compile y se vuelva a ejecutar la aplicación. Las declaraciones SQL generadas por Fluent se registrarán en la consola.
|
||||
|
||||
## Siguentes pasos
|
||||
|
||||
Felicitaciones por crear los primeros modelos, migraciones y realizar operaciones básicas de creación y lectura. Para obtener información más detallada sobre todas estas características, consultar las secciones correspondientes en la guía de Fluent.
|
||||
|
|
@ -135,6 +135,8 @@ try await sun.$planets.create(earth, on: database)
|
|||
让我们看一个 `Planet` 和 `Tag` 之间的多对多关系的例子。
|
||||
|
||||
```swift
|
||||
enum PlanetTagStatus: String, Codable { case accepted, pending }
|
||||
|
||||
// pivot 模型示例。
|
||||
final class PlanetTag: Model {
|
||||
static let schema = "planet+tag"
|
||||
|
|
@ -150,7 +152,7 @@ final class PlanetTag: Model {
|
|||
|
||||
init() { }
|
||||
|
||||
init(id: UUID? = nil, planet: Planet, tag: Tag) throws {
|
||||
init(id: UUID? = nil, planet: Planet, tag: Tag, comments: String?, status: PlanetTagStatus?) throws {
|
||||
self.id = id
|
||||
self.$planet.id = try planet.requireID()
|
||||
self.$tag.id = try tag.requireID()
|
||||
|
|
@ -158,7 +160,7 @@ final class PlanetTag: Model {
|
|||
}
|
||||
```
|
||||
|
||||
Pivots 是包含两个 `@Parent` 关系的一般模型。一个用于每个要关联的模型。如果需要,可以将其他属性存储在 pivot 上。
|
||||
任何包含至少两个 `@Parent` 关系的模型,每个关系对应两个要关联的模型,可以作为一个枢纽(pivot)。该模型可以包含其他属性,例如其 ID,甚至可以包含其他 `@Parent` 关系。
|
||||
|
||||
向 pivot 模型添加 [unique](schema.zh.md#unique) 约束有助于防止冗余条目。请参阅[模式](schema.zh.md)了解更多信息。
|
||||
|
||||
|
|
@ -197,13 +199,24 @@ final class Tag: Model {
|
|||
|
||||
`@Siblings` 属性具有从关系中添加和删除模型的方法。
|
||||
|
||||
使用 `attach` 方法向关系添加一个模型。这将自动创建并保存 `pivot` 模型。
|
||||
使用 `attach()` 方法将单个模型或模型数组添加到关系中。需要时,会自动创建并保存枢纽模型。可以指定回调闭包以填充每个创建的枢纽模型的其他属性。
|
||||
|
||||
```swift
|
||||
let earth: Planet = ...
|
||||
let inhabited: Tag = ...
|
||||
// 添加模型到关系中。
|
||||
try await earth.$tags.attach(inhabited, on: database)
|
||||
// 在建立关联时填充枢纽模型的属性。
|
||||
try await earth.$tags.attach(inhabited, on: database) { pivot in
|
||||
pivot.comments = "This is a life-bearing planet."
|
||||
pivot.status = .accepted
|
||||
}
|
||||
// 将带有属性的多个模型添加到关系中。
|
||||
let volcanic: Tag = ..., oceanic: Tag = ...
|
||||
try await earth.$tags.attach([volcanic, oceanic], on: database) { pivot in
|
||||
pivot.comments = "This planet has a tag named \(pivot.$tag.name)."
|
||||
pivot.status = .pending
|
||||
}
|
||||
```
|
||||
|
||||
附加单个模型时,你可以使用 `method` 参数选择是否在保存之前检查关系。
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
# Estructura de Carpetas
|
||||
|
||||
Una vez has creado, compilado y ejecutado tu primera app de Vapor, vamos a familiarizarte con la estructura de carpetas de Vapor. La estructura está basada en la utilizada por [SPM](spm.md), así que si has trabajado anteriormente con SPM debería ser algo conocido.
|
||||
|
||||
```
|
||||
.
|
||||
├── Public
|
||||
├── Sources
|
||||
│ ├── App
|
||||
│ │ ├── Controllers
|
||||
│ │ ├── Migrations
|
||||
│ │ ├── Models
|
||||
│ │ ├── configure.swift
|
||||
│ │ ├── entrypoint.swift
|
||||
│ │ └── routes.swift
|
||||
│
|
||||
├── Tests
|
||||
│ └── AppTests
|
||||
└── Package.swift
|
||||
```
|
||||
|
||||
Las secciones siguientes explican de manera más detallada cada parte de la estructura de carpetas.
|
||||
|
||||
## Public
|
||||
|
||||
Esta carpeta contiene los ficheros públicos que serán servidos por tu app si `FileMiddleware` está activado. Suelen ser imágenes, hojas de estilo o scripts de navegador. Por ejemplo, una petición a `localhost:8080/favicon.ico` comprobará si `Public/favicon.ico` existe y lo devolverá.
|
||||
|
||||
Necesitarás habilitar `FileMiddleware` en tu fichero `configure.swift` para que Vapor pueda servir ficheros públicos.
|
||||
|
||||
```swift
|
||||
// Serves files from `Public/` directory
|
||||
let fileMiddleware = FileMiddleware(
|
||||
publicDirectory: app.directory.publicDirectory
|
||||
)
|
||||
app.middleware.use(fileMiddleware)
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
Esta carpeta contiene todos los ficheros Swift de código fuente para tu proyecto.
|
||||
La carpeta `App`, de nivel superior, corresponde al módulo de tu package,
|
||||
tal y como está declarado en el manifiesto de [SwiftPM](spm.md).
|
||||
|
||||
### App
|
||||
|
||||
Aquí es donde se aloja toda la lógica de tu app.
|
||||
|
||||
#### Controllers
|
||||
|
||||
Los controladores son una buena manera de agrupar lógica de aplicación. La mayoría de controladores tienen muchas funciones que aceptan una petición y devuelven algún tipo de respuesta.
|
||||
|
||||
#### Migrations
|
||||
|
||||
En esta carpeta es donde se ubican las migraciones de tu base de datos si estás usando Fluent.
|
||||
|
||||
#### Models
|
||||
|
||||
La carpeta **models** es un buen lugar en el que guardar los structs `Content` o los `Model` de Fluent.
|
||||
|
||||
#### configure.swift
|
||||
|
||||
Este fichero contiene la función `configure(_:)`. Este método es llamado por `entrypoint.swift` para configurar la `Application` recién creada. Aquí es donde deberías registrar servicios como rutas, bases de datos, proveedores y otros.
|
||||
|
||||
#### entrypoint.swift
|
||||
|
||||
Este fichero contiene el punto de entrada `@main` para la aplicación, que crea, configura y ejecuta tu aplicación Vapor.
|
||||
|
||||
#### routes.swift
|
||||
|
||||
Este fichero contiene la función `routes(_:)`. Este método es llamado casi al final de `configure(_:)` para registrar rutas en tu `Application`.
|
||||
|
||||
## Tests
|
||||
|
||||
Cada módulo no ejecutable en tu carpeta `Sources` puede tener una carpeta correspondiente en `Tests`. Contiene código del módulo `XCTest` para hacer testing a tu package. Los tests pueden ser ejecutados usando `swift test` en la línea de comandos o pulsando ⌘+U en Xcode.
|
||||
|
||||
### AppTests
|
||||
|
||||
Esta carpeta contiene los tests unitarios para el código en tu módulo `App`.
|
||||
|
||||
## Package.swift
|
||||
|
||||
Finalmente está el manifiesto del package [SPM](spm.md).
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
# Hola, mundo
|
||||
|
||||
Esta guía te llevará paso a paso por el proceso de creación, construcción y ejecución de un nuevo proyecto de Vapor.
|
||||
|
||||
Si todavía no has instalado Swift o Vapor Toolbox, echa un vistazo a la sección de instalaciones.
|
||||
|
||||
- [Instalación → macOS](../install/macos.md)
|
||||
- [Instalación → Linux](../install/linux.md)
|
||||
|
||||
## Nuevo Proyecto
|
||||
|
||||
El primer paso es crear un nuevo proyecto de Vapor en tu computadora. Abre el terminal y usa el comando de nuevo proyecto de Toolbox. Esto generará una nueva carpeta con el proyecto en el directorio actual.
|
||||
|
||||
```sh
|
||||
vapor new hello -n
|
||||
```
|
||||
|
||||
!!! tip "Consejo"
|
||||
La marca `-n` te da una plantilla básica contestando negativamente a todas las preguntas de manera automática.
|
||||
|
||||
!!! tip "Consejo"
|
||||
También puedes obtener la plantilla más reciente desde GitHub sin usar Vapor Toolbox clonando [template respository](https://github.com/vapor/template-bare)
|
||||
|
||||
!!! tip "Consejo"
|
||||
Vapor y la plantilla ahora usan `async`/`await` por defecto.
|
||||
Si no puedes actualizar a macOS 12 y/o necesitas seguir usando los `EventLoopFuture`,
|
||||
usa la marca `--branch macos10-15`.
|
||||
|
||||
Cuando el comando haya terminado, cambia a la nueva carpeta recién creada:
|
||||
|
||||
|
||||
```sh
|
||||
cd hello
|
||||
```
|
||||
|
||||
## Compilar y Ejecutar
|
||||
|
||||
### Xcode
|
||||
|
||||
Primero, abre el proyecto en Xcode:
|
||||
|
||||
```sh
|
||||
open Package.swift
|
||||
```
|
||||
|
||||
Automáticamente comenzará a descargar las dependencias de Swift Package Manager. Este proceso puede requerir cierto tiempo la primera vez que abras el proyecto. Cuando la resolución de dependencias se haya completado Xcode poblará los esquemas disponibles.
|
||||
|
||||
En la parte superior de la ventana, a la derecha de los botones Play y Stop, pulsa en el nombre de tu proyecto para seleccionar el esquema (Scheme) del proyecto, y selecciona un target de ejecución apropiado—preferiblemente, "My Mac". Pulsa en el botón de play para compilar y ejecutar tu proyecto.
|
||||
|
||||
La consola debería aparecer en la parte inferior de la ventana de Xcode.
|
||||
|
||||
```sh
|
||||
[ INFO ] Server starting on http://127.0.0.1:8080
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
En Linux y otros sistemas operativos (e inclusive en macOS si no quieres usar Xcode) puedes editar el proyecto en el editor que prefieras, por ejemplo Vim o VSCode. Visita [Swift Server Guides](https://github.com/swift-server/guides/blob/main/docs/setup-and-ide-alternatives.md) para detalles actualizados sobre cómo configurar otros IDEs.
|
||||
|
||||
Para construir y ejecutar tu proyecto, ejecuta en el Terminal:
|
||||
|
||||
```sh
|
||||
swift run
|
||||
```
|
||||
|
||||
Eso compilará y ejecutará tu proyecto. La primera vez que lo ejecutes necesitará un tiempo para buscar y resolver las dependencias. Una vez esté ejecutándose deberías ver en la consola lo siguiente:
|
||||
|
||||
```sh
|
||||
[ INFO ] Server starting on http://127.0.0.1:8080
|
||||
```
|
||||
|
||||
## Visitar Localhost
|
||||
|
||||
Abre tu navegador web y dirígete <a href="http://localhost:8080/hello" target="_blank">localhost:8080/hello</a> or <a href="http://127.0.0.1:8080" target="_blank">http://127.0.0.1:8080</a>
|
||||
|
||||
Deberías ver la página a continuación.
|
||||
|
||||
```html
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
¡Enhorabuena! ¡Has creado, compilado y ejecutado tu primera app de Vapor! 🎉
|
||||
|
|
@ -19,6 +19,9 @@ vapor new hello -n
|
|||
!!! tip "建议"
|
||||
使用 `-n` 为所有的问题自动选择 no 来为您提供一个基本的模板。
|
||||
|
||||
!!! tip "建议"
|
||||
你也可以不使用 Vapor Toolbox,直接从 GitHub 克隆[模板库](https://github.com/vapor/template-bare)来获取最新的模板。
|
||||
|
||||
!!! tip "建议"
|
||||
Vapor 以及自带的模板默认使用 `async`/`await`。如果你的系统不能更新到 macOS 12 或者想继续使用 `EventLoopFuture`。运行命令时可以添加此标志 `--branch macos10-15`。
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
# Swift Package Manager
|
||||
|
||||
El [Swift Package Manager](https://swift.org/package-manager/) (SPM) es usado para compilar el código fuente y las dependencias de tu proyecto. Dado que Vapor depende en gran medida de SPM, entender su funcionamiento básico es una buena idea.
|
||||
|
||||
SPM es similar a Cocoapods, Ruby gems y NPM. Puedes usar SPM desde la línea de comandos con comandos como `swift build` y `swift test`, o con IDEs compatibles. Sin embargo, a diferencia de otros package managers, no hay un índice de paquete central para los paquetes de SPM. En su lugar, SPM utiliza URLs de repositorios Git y dependencias de versiones utilizando [Git tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging).
|
||||
|
||||
## Package Manifest
|
||||
|
||||
El primer lugar en el que SPM busca en tu proyecto es el package manifest. Éste debería estar alojado siempre en el directorio raíz de tu proyecto y llamarse `Package.swift`.
|
||||
|
||||
Echa un vistazo a este ejemplo de Package manifest.
|
||||
|
||||
```swift
|
||||
// swift-tools-version:5.8
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MyApp",
|
||||
platforms: [
|
||||
.macOS(.v12)
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/vapor/vapor.git", from: "4.76.0"),
|
||||
],
|
||||
targets: [
|
||||
.executableTarget(
|
||||
name: "App",
|
||||
dependencies: [
|
||||
.product(name: "Vapor", package: "vapor")
|
||||
]
|
||||
),
|
||||
.testTarget(name: "AppTests", dependencies: [
|
||||
.target(name: "App"),
|
||||
.product(name: "XCTVapor", package: "vapor"),
|
||||
])
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
Cada parte del manifiesto se explica en las secciones a continuación.
|
||||
|
||||
### Versión de Herramientas
|
||||
|
||||
La primera línea del package manifest indica la versión requerida de las herramientas de Swift. Esto especifica la versión mínima de Swift que el paquete soporta. La API de descripción del paquete puede sufrir cambios entre versiones de Swift, así que esta línea asegura que Swift sepa analizar tu manifiesto.
|
||||
|
||||
### Nombre del Paquete
|
||||
|
||||
El primer argumento de `Package` es el nombre del paquete. Si el paquete es público, deberás usar el último segmento de la URL del repositorio de Git como nombre.
|
||||
|
||||
### Plataformas
|
||||
|
||||
El array `platforms` especifica las plataformas que el paquete soporta. Especificando `.macOS(.v12)` este paquete requiere macOS 12 o superior. Cuando Xcode cargue este proyecto, ajustará automáticamente la versión mínima de despliegue a macOS 12 para que puedas usar todas las APIs disponibles.
|
||||
|
||||
### Dependencias
|
||||
|
||||
Las dependencias son otros paquetes de SPM de los que tu paquete depende. Todas las aplicaciones de Vapor dependen del paquete Vapor, pero puedes agregar tantas dependencias como quieras.
|
||||
|
||||
En el ejemplo anterior, puedes ver que [vapor/vapor](https://github.com/vapor/vapor), en su versión 4.76.0 o superior, es una dependencia de este paquete. Al agregar una dependencia a tu paquete, deberás señalar a continuación los [targets](#targets) que dependen
|
||||
de los módulos recién agregados.
|
||||
|
||||
### Targets
|
||||
|
||||
Los targets son todos los módulos, ejecutables y tests que tu paquete contiene. La mayoría de aplicaciones Vapor tendrán dos targets, aunque puedes añadir tantos como quieras para organizar tu código. Cada target declara los módulos de los que depende. Debes añadir los nombres de los módulos aquí para poder importarlos en tu código. Un target puede depender de otros targets en tu proyecto o de cualquiera de los módulos expuestos por los paquetes que hayas agregado en
|
||||
el array de [main dependencies](#dependencies).
|
||||
|
||||
## Estructura de Carpetas
|
||||
|
||||
A continuación se muestra la estructura de carpetas típica para un paquete SPM.
|
||||
|
||||
```
|
||||
.
|
||||
├── Sources
|
||||
│ └── App
|
||||
│ └── (Source code)
|
||||
├── Tests
|
||||
│ └── AppTests
|
||||
└── Package.swift
|
||||
```
|
||||
|
||||
Cada `.target` o `.executableTarget` se corresponde con una carpeta en la carpeta `Sources`.
|
||||
Cada `.testTarget` se corresponde con una carpeta en la carpeta `Tests`.
|
||||
|
||||
## Package.resolved
|
||||
|
||||
La primera vez que construyas tu proyecto, SPM creará un fichero `Package.resolved` que guarda la versión de cada dependencia. La próxima vez que construyas tu proyecto, estas mismas versiones serán usadas aunque haya versiones nuevas disponibles.
|
||||
|
||||
Para actualizar tus dependencias, ejecuta `swift package update`.
|
||||
|
||||
## Xcode
|
||||
|
||||
Si estás usando Xcode 11 o superior, los cambios en dependencias, targets, productos y demás se harán de manera automática cuando el fichero `Package.swift` sea modificado.
|
||||
|
||||
Si quieres actualizar las dependencias, ve a File → Swift Packages → Update To Latest Swift Package Versions.
|
||||
|
||||
En general, es recomendable añadir el fichero `.swiftpm` a tu `.gitignore`. En este fichero se guardará la configuración de tu proyecto de Xcode.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# Xcode
|
||||
|
||||
Esta página repasa algunos trucos y consejos para usar Xcode. Si usas un entorno de desarrollo distinto, puedes omitir esta parte.
|
||||
|
||||
## Custom Working Directory
|
||||
|
||||
Por defecto, Xcode ejecutará tu proyecto desde la carpeta _DerivedData_. Esta carpeta es otra distinta a la carpeta raíz de tu proyecto (donde se encuentra tu fichero _Package.swift_). Esto quiere decir que Vapor no será capaz de encontrar ficheros y carpetas como _.env_ o _Public_.
|
||||
|
||||
Puedes averiguar que esto está sucediendo si al ejecutar tu proyecto recibes el siguiente aviso.
|
||||
|
||||
```fish
|
||||
[ WARNING ] No custom working directory set for this scheme, using /path/to/DerivedData/project-abcdef/Build/
|
||||
```
|
||||
|
||||
Para solucionarlo, establece un directorio de trabajo personalizado para tu proyecto en el esquema de Xcode.
|
||||
|
||||
Primero, edita el esquema de tu proyecto pulsando en el selector de esquemas junto a los botones de play y stop.
|
||||
|
||||

|
||||
|
||||
Selecciona _Edit Scheme..._ en el menú desplegable.
|
||||
|
||||

|
||||
|
||||
En el editor de esquemas, elige la acción _App_ y la pestaña _Options_. Selecciona _Use custom working directory_ e ingresa la dirección de la carpeta raíz de tu proyecto.
|
||||
|
||||

|
||||
|
||||
Puedes obtener la dirección completa a la raíz de tu proyecto ejecutando `pwd` en una ventana de terminal ubicada en el proyecto.
|
||||
|
||||
```fish
|
||||
# verificar que estamos en la carpeta del proyecto de vapor
|
||||
vapor --version
|
||||
# obtener la dirección de la carpeta
|
||||
pwd
|
||||
```
|
||||
|
||||
Deberías obtener una salida similar a la que se muestra a continuación.
|
||||
|
||||
```
|
||||
framework: 4.x.x
|
||||
toolbox: 18.x.x
|
||||
/path/to/project
|
||||
```
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
Witaj w dokumentacji Vapor! Vapor jest frameworkiem do tworzenia aplikacji internetowych z wykorzystaniem języka Swift. Pozwala na pisanie serwisów backendowych, API internetowych i serwerów HTTP w Swifcie.
|
||||
|
||||
## Jak zacząć
|
||||
|
||||
Jeśli to jest twój pierwszy raz z Vapor, kieruj się do [Instalacja → macOS](install/macos.md) aby, zainstalować Swift i Vapor.
|
||||
Kiedy już zainstalujesz Vapor, sprawdź [Jak zacząć → Witaj, świecie](getting-started/hello-world.md) aby stworzyć twoja pierwsza aplikacje z użyciem Vapor!
|
||||
|
||||
## Inne źródła
|
||||
|
||||
Tutaj znajdziesz inne świetne źródła informacji o Vapor.
|
||||
|
||||
| nazwa | opis | link |
|
||||
|----------------|--------------------------------------------------|-------------------------------------------------------------------|
|
||||
| Vapor Discord | Rozmawiaj z tysiącami deweloperów. | [zobacz →](https://vapor.team) |
|
||||
| Dokumentacja API | Samo generująca się dokumentacja z komentarzy do kodu. | [zobacz →](https://api.vapor.codes) |
|
||||
| Stack Overflow | Zadawaj i odpowiadaj na pytania z tagiem `vapor`. | [zobacz →](https://stackoverflow.com/questions/tagged/vapor) |
|
||||
| Forum Swift | Zapostuj w sekcji Vapor na forum Swift.org. | [zobacz →](https://forums.swift.org/c/related-projects/vapor) |
|
||||
| Kod źródłowy | Naucz się jak Vapor działa pod maską. | [zobacz →](https://github.com/vapor/vapor) |
|
||||
| GitHub Issues | Zgłaszaj błędy oraz prośby na GitHub. | [zobacz →](https://github.com/vapor/vapor/issues) |
|
||||
|
||||
## Stara dokumentacja
|
||||
|
||||
Dokumentacja dla wcześniejszych wersji Vapor, które osiągnęły swój koniec życia można znaleźć na [https://legacy.docs.vapor.codes/](https://legacy.docs.vapor.codes/).
|
||||
|
||||
## Autorzy
|
||||
|
||||
The Vapor Core Team, oraz setki członków społeczności wokoło Vapor.
|
||||
|
|
@ -1,16 +1,15 @@
|
|||
# Installation unter Linux
|
||||
|
||||
Die Mindestvorraussetzung für Vapor ist Swift 5.2 oder höher. Daher läuft das Framework auch auf allen Linux-Distributionen, die ebenfalls Swift 5.2 oder höher unterstützen. Unter [Swift.org](https://swift.org/download/) kannst du Swift für Linux-Distributionen herunterladen und installieren.
|
||||
Die Mindestvorraussetzung für Vapor ist Swift 5.6 oder höher. Daher läuft das Framework auch auf allen Linux-Distributionen, die ebenfalls Swift 5.6 oder höher unterstützen. Unter [Swift.org](https://swift.org/download/) kannst du Swift für Linux-Distributionen herunterladen und installieren.
|
||||
|
||||
Hier findest du eine Übersicht der untersützten Linux-Distribution:
|
||||
|
||||
|Distribution |Version |Swift Version|
|
||||
|-----------------------------------------------------------------------------------|---------------|-------------|
|
||||
|Ubuntu. |20.04 |>= 5.2.4. |
|
||||
|Ubuntu |16.04, 18.04. |>= 5.2. |
|
||||
|Fedora. |>= 30 |>= 5.2 |
|
||||
|CentOS. |8. |>= 5.2.4. |
|
||||
|Amazon Linux |2. |>= 5.2.4. |
|
||||
|Ubuntu. |20.04 |>= 5.6. |
|
||||
|Fedora. |>= 30 |>= 5.6 |
|
||||
|CentOS. |8. |>= 5.6. |
|
||||
|Amazon Linux |2. |>= 5.6. |
|
||||
|_Die Angaben können abweichen. Für offizielle Daten siehe [Swift Releases](https://swift.org/download/#releases)_|
|
||||
|
||||
Es kann gut möglich sein, dass Swift auch auf Distributionen läuft, die nicht offiziell gelistet werden, allerdings können wir das nicht garantieren. Mehr Informationen dazu, findest du unter [Swift - Github](https://github.com/apple/swift#getting-started).
|
||||
|
|
@ -27,7 +26,7 @@ Fedora-Nutzer können folgenden Befehl ausführen
|
|||
sudo dnf install swift-lang
|
||||
```
|
||||
|
||||
Solltest du jedoch Fedora 30 verwenden, benötigst du EPEL 8 um Swift 5.2 zum Laufen zu bringen.
|
||||
Solltest du jedoch Fedora 30 verwenden, benötigst du EPEL 8 um Swift 5.6 zum Laufen zu bringen.
|
||||
|
||||
### - Docker
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
# Instalación en Linux
|
||||
|
||||
Para usar Vapor, necesitas Swift 5.6 o superior. Se puede instalar usando las opciones disponibles en [Swift.org](https://swift.org/download/).
|
||||
|
||||
## Distribuciones y Versiones Soportadas
|
||||
|
||||
Vapor admite las mismas versiones de distribución de Linux que adminte Swift 5.6 o versiones más recientes.
|
||||
|
||||
!!! nota
|
||||
Las versiones soportadas que se enumeran a continuación pueden quedar obsoletas en cualquier momento. Puedes comprobar qué sistemas operativos son oficialmente compatibles en la página [Swift Releases](https://swift.org/download/#releases).
|
||||
|
||||
|Distribución|Versión|Versión de Swift|
|
||||
|-|-|-|
|
||||
|Ubuntu|20.04|>= 5.6|
|
||||
|Fedora|>= 30|>= 5.6|
|
||||
|CentOS|8|>= 5.6|
|
||||
|Amazon Linux|2|>= 5.6|
|
||||
|
||||
Las distribuciones de Linux que no son oficialmente compatibles también pueden ejecutar Swift al compilar el código fuente, pero Vapor no puede garantizar estabilidad. Puedes aprender más sobre cómo compilar Swift desde [Swift Repo](https://github.com/apple/swift#getting-started).
|
||||
|
||||
## Instalar Swift
|
||||
|
||||
Visita la guía [Using Downloads](https://swift.org/download/#using-downloads) de Swift.org para ver las instrucciones de cómo instalar Swift en Linux.
|
||||
|
||||
### Fedora
|
||||
|
||||
Los usuarios de Fedora pueden simplemente utilizar el siguiente comando para instalar Swift:
|
||||
|
||||
```sh
|
||||
sudo dnf install swift-lang
|
||||
```
|
||||
|
||||
Si utilizas Fedora 30, deberás agregar EPEL 8 para obtener Swift 5.6 o versiones más nuevas.
|
||||
|
||||
## Docker
|
||||
|
||||
También puedes usar las imágenes de Docker oficiales de Swift que vienen con el compilador preinstalado. Obtenga más información en [Swift's Docker Hub](https://hub.docker.com/_/swift).
|
||||
|
||||
## Instalar Toolbox
|
||||
|
||||
Ahora que tienes Swift instalado, vamos a instalar [Vapor Toolbox](https://github.com/vapor/toolbox). Esta aplicación de línea de comando (CLI) no es necesaria para usar Vapor, pero incluye útiles herramientas.
|
||||
|
||||
En Linux deberás compilar Toolbox desde el código fuente. Puedes ver las <a href="https://github.com/vapor/toolbox/releases" target="_blank">versiones</a> de Toolbox en GitHub para encontrar la versión más reciente.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/vapor/toolbox.git
|
||||
cd toolbox
|
||||
git checkout <desired version>
|
||||
make install
|
||||
```
|
||||
|
||||
Verifica que la instalación fue exitosa imprimiendo la ayuda.
|
||||
|
||||
```sh
|
||||
vapor --help
|
||||
```
|
||||
|
||||
Deberías ver una lista de comandos disponibles.
|
||||
|
||||
## Siguientes Pasos
|
||||
|
||||
Después de instalar Swift, crea tu primera app en [Comenzando → Hola, mundo](../getting-started/hello-world.md).
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
# Installazione su Linux
|
||||
|
||||
Per usare Vapor, avrete bisogno di Swift 5.2 o superiore. Potete installarlo usando le toolchains disponibili su [Swift.org](https://swift.org/download/)
|
||||
|
||||
## Distribuzioni e Versioni supportate
|
||||
|
||||
Vapor supporta le stesse versioni delle distribuzioni Linux che supportano Swift 5.2 o versioni più recenti.
|
||||
|
||||
!!! note
|
||||
Le versioni supportate elencate di seguito potrebbero essere obsolete in qualsiasi momento. Potete controllare quali sistemi operativi sono ufficialmente supportati sulla pagina [Swift Releases](https://swift.org/download/#releases).
|
||||
|
||||
|Distribuzione|Versione|Versione di Swift|
|
||||
|-|-|-|
|
||||
|Ubuntu|16.04, 18.04|>= 5.2|
|
||||
|Ubuntu|20.04|>= 5.2.4|
|
||||
|Fedora|>= 30|>= 5.2|
|
||||
|CentOS|8|>= 5.2.4|
|
||||
|Amazon Linux|2|>= 5.2.4|
|
||||
|
||||
Le distribuzioni Linux non ufficialmente supportate possono comunque eseguire Swift compilando il codice sorgente, ma Vapor non può garantirne la stabilità. Potete saperne di più sulla compilazione di Swift dal [repo di Swift](https://github.com/apple/swift#getting-started).
|
||||
|
||||
## Installare Swift
|
||||
|
||||
Visitate la guida [Using Downloads](https://swift.org/download/#using-downloads) di Swift.org per le istruzioni su come installare Swift su Linux.
|
||||
|
||||
### Fedora
|
||||
|
||||
Gli utenti Fedora possono semplicemente usare il seguente comando per installare Swift:
|
||||
|
||||
```sh
|
||||
sudo dnf install swift-lang
|
||||
```
|
||||
|
||||
Se state usando Fedora 30, dovrete aggiungere EPEL 8 per ottenere Swift 5.2 o versioni più recenti.
|
||||
|
||||
## Docker
|
||||
|
||||
Potete anche usare le immagini Docker ufficiali di Swift che includono il compilatore preinstallato. Potete saperne di più sul [Docker Hub di Swift](https://hub.docker.com/_/swift).
|
||||
|
||||
## Installare la Toolbox
|
||||
|
||||
Ora che avete installato Swift, potete installare la [Toolbox di Vapor](https://github.com/vapor/toolbox). Questo strumento CLI non è necessario per usare Vapor, ma include degli strumenti utili.
|
||||
|
||||
Su Linux, dovrete compilare la toolbox dal codice sorgente. Guardate le <a href="https://github.com/vapor/toolbox/releases" target="_blank"> release </a> della toolbox su GitHub per trovare l'ultima versione.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/vapor/toolbox.git
|
||||
cd toolbox
|
||||
git checkout <desired version>
|
||||
make install
|
||||
```
|
||||
|
||||
Controllate che l'installazione sia andata a buon fine stampando l'aiuto.
|
||||
|
||||
```sh
|
||||
vapor --help
|
||||
```
|
||||
|
||||
Dovreste vedere una lista di comandi disponibili.
|
||||
|
||||
## Come continuare
|
||||
|
||||
Dopo aver installato Vapor, potete iniziare a creare il vostro primo progetto usando [Inizio → Ciao, mondo](../getting-started/hello-world.it.md).
|
||||
|
|
@ -1,21 +1,20 @@
|
|||
# Install on Linux
|
||||
|
||||
To use Vapor, you will need Swift 5.2 or greater. This can be installed using the toolchains available on [Swift.org](https://swift.org/download/)
|
||||
To use Vapor, you will need Swift 5.6 or greater. This can be installed using the toolchains available on [Swift.org](https://swift.org/download/)
|
||||
|
||||
## Supported Distributions and Versions
|
||||
|
||||
Vapor supports the same versions of Linux distributions that Swift 5.2 or newer versions supports.
|
||||
Vapor supports the same versions of Linux distributions that Swift 5.6 or newer versions supports.
|
||||
|
||||
!!! note
|
||||
The supported versions listed below may be outdated at any time. You can check which operating systems are officially supported on the [Swift Releases](https://swift.org/download/#releases) page.
|
||||
|
||||
|Distribution|Version|Swift Version|
|
||||
|-|-|-|
|
||||
|Ubuntu|16.04, 18.04|>= 5.2|
|
||||
|Ubuntu|20.04|>= 5.2.4|
|
||||
|Fedora|>= 30|>= 5.2|
|
||||
|CentOS|8|>= 5.2.4|
|
||||
|Amazon Linux|2|>= 5.2.4|
|
||||
|Ubuntu|20.04|>= 5.6|
|
||||
|Fedora|>= 30|>= 5.6|
|
||||
|CentOS|8|>= 5.6|
|
||||
|Amazon Linux|2|>= 5.6|
|
||||
|
||||
Linux distributions not officially supported may also run Swift by compiling the source code, but Vapor cannot prove stability. Learn more about compiling Swift from the [Swift repo](https://github.com/apple/swift#getting-started).
|
||||
|
||||
|
|
@ -31,7 +30,7 @@ Fedora users can simply use the following command to install Swift:
|
|||
sudo dnf install swift-lang
|
||||
```
|
||||
|
||||
If you're using Fedora 30, you'll need to add EPEL 8 to get Swift 5.2 or newer versions.
|
||||
If you're using Fedora 30, you'll need to add EPEL 8 to get Swift 5.6 or newer versions.
|
||||
|
||||
## Docker
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
# Installeren op Linux
|
||||
|
||||
Om Vapor te gebruiken op Linux, zal je Swift 5.2 of hoger nodig hebben. Dit kan geïnstalleerd worden met de toolchains te vinden op [Swift.org](https://swift.org/download/).
|
||||
Om Vapor te gebruiken op Linux, zal je Swift 5.6 of hoger nodig hebben. Dit kan geïnstalleerd worden met de toolchains te vinden op [Swift.org](https://swift.org/download/).
|
||||
|
||||
## Ondersteunde distributies en versies
|
||||
|
||||
Vapor ondersteund dezelfde versies van Linux distributies die Swift 5.2 of hogere versies ook ondersteunen.
|
||||
Vapor ondersteund dezelfde versies van Linux distributies die Swift 5.6 of hogere versies ook ondersteunen.
|
||||
|
||||
!!! Opmerking
|
||||
The ondersteunde versies hieronder kunnen op elke moment verouderd zijn. Je kan zien welke besturingssystemen officiele ondersteuning krijgen op de [Swift Releases](https://swift.org/download/#releases/) pagina.
|
||||
|
||||
|Distribution|Version|Swift Version|
|
||||
|-|-|-|
|
||||
|Ubuntu|16.04, 18.04|>= 5.2|
|
||||
|Ubuntu|20.04|>= 5.2.4|
|
||||
|Fedora|>= 30|>= 5.2|
|
||||
|CentOS|8|>= 5.2.4|
|
||||
|Amazon Linux|2|>= 5.2.4|
|
||||
|Ubuntu|20.04|>= 5.6|
|
||||
|Fedora|>= 30|>= 5.6|
|
||||
|CentOS|8|>= 5.6|
|
||||
|Amazon Linux|2|>= 5.6|
|
||||
|
||||
Linux distributies die niet officieel ondersteund zijn kunnen mogelijks ook Swift uitvoeren door de broncode te compileren, maar Vapor kan geen stabiliteit garanderen. Meer informatie over het compileren van Swift kan gevonden worden op de [Swift repo](https://github.com/apple/swift#getting-started).
|
||||
|
||||
|
|
@ -31,7 +30,7 @@ Fedora gebruikers kunnen eenvoudig het volgende commando gebruiken om Swift te i
|
|||
sudo dnf install swift-lang
|
||||
```
|
||||
|
||||
Als je Fedore 30 gebruikt, dan zal je EPEL 8 moeten toevoegen om Swift 5.2 of nieuwere versies te krijgen.
|
||||
Als je Fedore 30 gebruikt, dan zal je EPEL 8 moeten toevoegen om Swift 5.6 of nieuwere versies te krijgen.
|
||||
|
||||
## Docker
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
|
||||
# 在 Linux 上面安装
|
||||
|
||||
你需要 Swift 5.2 或更高版本来使用 Vapor。可以通过 [Swift.org](https://swift.org/download/) 上面的工具链来安装。
|
||||
你需要 Swift 5.6 或更高版本来使用 Vapor。可以通过 [Swift.org](https://swift.org/download/) 上面的工具链来安装。
|
||||
|
||||
## 支持的发行版和版本
|
||||
|
||||
Vapor 与 Swift 5.2 或者更高的版本对 Linux 的版本支持保持一致。
|
||||
Vapor 与 Swift 5.6 或者更高的版本对 Linux 的版本支持保持一致。
|
||||
|
||||
!!! note "注意"
|
||||
下面列出的版本可能会随时过期。你可以到 [Swift Releases](https://swift.org/download/#releases) 官方网站去确认官方支持的操作系统。
|
||||
|
||||
|Distribution|Version|Swift Version|
|
||||
|-|-|-|
|
||||
|Ubuntu|16.04, 18.04|>= 5.2|
|
||||
|Ubuntu|20.04|>= 5.2.4|
|
||||
|Fedora|>= 30|>= 5.2|
|
||||
|CentOS|8|>= 5.2.4|
|
||||
|Amazon Linux|2|>= 5.2.4|
|
||||
|Ubuntu|20.04|>= 5.6|
|
||||
|Fedora|>= 30|>= 5.6|
|
||||
|CentOS|8|>= 5.6|
|
||||
|Amazon Linux|2|>= 5.6|
|
||||
|
||||
不受官方支持的 Linux 发行版也可以通过编译源代码来运行 Swift,但是 Vapor 不能保证其稳定性。可以在 [Swift repo](https://github.com/apple/swift#getting-started) 学习更多关于编译 Swift 的信息。
|
||||
|
||||
|
|
@ -32,7 +31,7 @@ Fedora 用户可以简单的通过下面的命令来安装 Swift:
|
|||
sudo dnf install swift-lang
|
||||
```
|
||||
|
||||
如果你正在使用 Fedora 30,你需要添加添加 EPEL 8 来获取 Swift 5.2 或更新的版本。
|
||||
如果你正在使用 Fedora 30,你需要添加添加 EPEL 8 来获取 Swift 5.6 或更新的版本。
|
||||
|
||||
|
||||
## Docker
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
# Instalación en macOS
|
||||
|
||||
Para usar Vapor en macOS, necesitarás Swift 5.6 o superior. Swift y todas sus dependencias vienen incluidas con Xcode.
|
||||
|
||||
## Instalar Xcode
|
||||
|
||||
Instala [Xcode](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) desde la Mac App Store.
|
||||
|
||||

|
||||
|
||||
Una vez que se haya descargado Xcode, debes abrirlo para completar la instalación. Este paso puede tardar un rato.
|
||||
|
||||
Para asegurarse de que la instalación se haya realizado correctamente, abre la Terminal e imprime la versión de Swift.
|
||||
|
||||
```sh
|
||||
swift --version
|
||||
```
|
||||
|
||||
Deberías ver la información de la versión de Swift.
|
||||
|
||||
```sh
|
||||
swift-driver version: 1.75.2 Apple Swift version 5.8 (swiftlang-5.8.0.124.2 clang-1403.0.22.11.100)
|
||||
Target: arm64-apple-macosx13.0
|
||||
```
|
||||
|
||||
Vapor 4 requiere Swift 5.6 o superior.
|
||||
|
||||
## Instalar Toolbox
|
||||
|
||||
Ahora que tienes Swift instalado, vamos a instalar [Vapor Toolbox](https://github.com/vapor/toolbox). Esta aplicación de línea de comando (CLI) no es necesaria para usar Vapor, pero incluye útiles herramientas como un nuevo creador de proyectos.
|
||||
|
||||
Toolbox se distribuye a través de Homebrew. Si aún no tienes Homebrew, visita <a href="https://brew.sh" target="_blank">brew.sh</a> para obtener instrucciones de instalación.
|
||||
|
||||
```sh
|
||||
brew install vapor
|
||||
```
|
||||
|
||||
Verifica que la instalación fue exitosa imprimiendo la ayuda.
|
||||
|
||||
```sh
|
||||
vapor --help
|
||||
```
|
||||
|
||||
Deberías ver una lista de comandos disponibles.
|
||||
|
||||
|
||||
## Siguientes Pasos
|
||||
|
||||
Ahora que tienes instalados Swift y Vapor Toolbox, crea tu primera app en [Comenzando → Hola, mundo](../getting-started/hello-world.md).
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# Installazione su macOS
|
||||
|
||||
Per usare Vapor su macOS, avrete bisogno di Swift 5.6 o superiore. Swift e tutte le sue dipendenze vengono installati automaticamente quando si installa Xcode.
|
||||
|
||||
## Installare Xcode
|
||||
|
||||
Potete installare Xcode dal [Mac App Store](https://apps.apple.com/us/app/xcode/id497799835?mt=12).
|
||||
|
||||

|
||||
|
||||
Dopo aver scaricato Xcode, dovete aprirlo per completare l'installazione. Questo potrebbe richiedere un po' di tempo.
|
||||
|
||||
Controllate che l'installazione sia andata a buon fine aprendo il Terminale e stampando la versione di Swift.
|
||||
|
||||
```sh
|
||||
swift --version
|
||||
```
|
||||
|
||||
Dovreste vedere stampate le informazioni della versione di Swift:
|
||||
|
||||
```sh
|
||||
swift-driver version: 1.75.2 Apple Swift version 5.8 (swiftlang-5.8.0.124.2 clang-1403.0.22.11.100)
|
||||
Target: arm64-apple-macosx13.0
|
||||
```
|
||||
|
||||
Vapor 4 richiede Swift 5.6 o superiore.
|
||||
|
||||
## Installare la Toolbox
|
||||
|
||||
Ora che avete installato Swift, potete installare la [Vapor Toolbox](https://github.com/vapor/toolbox). Questo strumento da linea di comando non è necessario per usare Vapor, ma include strumenti utili come il creatore di nuovi progetti.
|
||||
|
||||
La toolbox è distribuita tramite Homebrew. Se non avete ancora Homebrew, visitate <a href="https://brew.sh" target="_blank">brew.sh</a> per le istruzioni di installazione.
|
||||
|
||||
```sh
|
||||
brew install vapor
|
||||
```
|
||||
|
||||
Controllate che l'installazione sia andata a buon fine stampando l'aiuto.
|
||||
|
||||
```sh
|
||||
vapor --help
|
||||
```
|
||||
|
||||
Dovreste vedere una lista di comandi disponibili.
|
||||
|
||||
## Come continuare
|
||||
|
||||
Dopo aver installato Vapor, potete iniziare a creare il vostro primo progetto usando [Inizio → Ciao, mondo](../getting-started/hello-world.it.md).
|
||||
|
|
@ -17,7 +17,7 @@ Leaf 标签由四个元素组成:
|
|||
- 标记 `#`:这表示 leaf 解析器开始寻找的标记。
|
||||
- 名称 `count`:标签的标识符。
|
||||
- 参数列表 `(users)`:可以接受零个或多个参数。
|
||||
- 正文: 可以使用分号和结束标签为某些标签提供可选的正文。
|
||||
- 正文: 可以使用冒号和结束标签为某些标签提供可选的正文。
|
||||
|
||||
根据标签的实现,这四个元素可以有许多不同的用法。让我们来看几个例子,Leaf 内置标签是如何使用的:
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[Redis](https://redis.io/) is one of the most popular in-memory data structure store commonly used as a cache or message broker.
|
||||
|
||||
This library is an integration between Vapor and [**RediStack**](https://gitlab.com/mordil/redistack), which is the underlying driver that communicates with Redis.
|
||||
This library is an integration between Vapor and [**RediStack**](https://github.com/swift-server/RediStack), which is the underlying driver that communicates with Redis.
|
||||
|
||||
!!! note
|
||||
Most of the capabilities of Redis are provided by **RediStack**.
|
||||
|
|
@ -32,7 +32,7 @@ targets: [
|
|||
|
||||
## Configure
|
||||
|
||||
Vapor employs a pooling strategy for [`RedisConnection`](https://swiftpackageindex.com/mordil/redistack/1.3.2/documentation/redistack/redisconnection) instances, and there are several options to configure individual connections as well as the pools themselves.
|
||||
Vapor employs a pooling strategy for [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisconnection) instances, and there are several options to configure individual connections as well as the pools themselves.
|
||||
|
||||
The bare minimum required for configuring Redis is to provide a URL to connect to:
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ This option determines the behavior of how the maximum connection count is maint
|
|||
|
||||
## Sending a Command
|
||||
|
||||
You can send commands using the `.redis` property on any [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) or [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) instance, which will give you access to a [`RedisClient`](https://swiftpackageindex.com/mordil/redistack/1.3.2/documentation/redistack/redisclient).
|
||||
You can send commands using the `.redis` property on any [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) or [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) instance, which will give you access to a [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisclient).
|
||||
|
||||
Any `RedisClient` has several extensions for all of the various [Redis commands](https://redis.io/commands).
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ There is a defined lifecycle to a subscription:
|
|||
1. **message**: invoked 0+ times as messages are published to the subscribed channels
|
||||
1. **unsubscribe**: invoked once when the subscription ends, either by request or the connection being lost
|
||||
|
||||
When you create a subscription, you must provide at least a [`messageReceiver`](https://swiftpackageindex.com/mordil/redistack/1.3.2/documentation/redistack/redissubscriptionmessagereceiver) to handle all messages that are published by the subscribed channel.
|
||||
When you create a subscription, you must provide at least a [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redissubscriptionmessagereceiver) to handle all messages that are published by the subscribed channel.
|
||||
|
||||
You can optionally provide a `RedisSubscriptionChangeHandler` for `onSubscribe` and `onUnsubscribe` to handle their respective lifecycle events.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[Redis](https://redis.io/) is een van de populairste in-memory data structuur opslagplaatsen die vaak gebruikt worden als een cache of message broker.
|
||||
|
||||
Deze bibliotheek is een integratie tussen Vapor en [**RediStack**](https://gitlab.com/mordil/redistack), wat de onderliggende driver is die communiceert met Redis.
|
||||
Deze bibliotheek is een integratie tussen Vapor en [**RediStack**](https://github.com/swift-server/RediStack), wat de onderliggende driver is die communiceert met Redis.
|
||||
|
||||
!!! note
|
||||
De meeste mogelijkheden van Redis worden geleverd door **RediStack**.
|
||||
|
|
@ -32,7 +32,7 @@ targets: [
|
|||
|
||||
## Configuratie
|
||||
|
||||
Vapor gebruikt een pooling strategie voor [`RedisConnection`](https://swiftpackageindex.com/mordil/redistack/1.3.2/documentation/redistack/redisconnection) instanties, en er zijn verschillende opties om zowel individuele verbindingen als de pools zelf te configureren.
|
||||
Vapor gebruikt een pooling strategie voor [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisconnection) instanties, en er zijn verschillende opties om zowel individuele verbindingen als de pools zelf te configureren.
|
||||
|
||||
Het absolute minimum dat nodig is voor het configureren van Redis is het opgeven van een URL om verbinding mee te maken:
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ Deze optie bepaalt het gedrag van hoe het maximum aantal verbindingen wordt bijg
|
|||
|
||||
## Een commando versturen
|
||||
|
||||
Je kunt commando's sturen met de `.redis` eigenschap op elke [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) of [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) instantie, die je toegang geeft tot een [`RedisClient`](https://swiftpackageindex.com/mordil/redistack/1.3.2/documentation/redistack/redisclient).
|
||||
Je kunt commando's sturen met de `.redis` eigenschap op elke [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) of [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) instantie, die je toegang geeft tot een [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisclient).
|
||||
|
||||
Elke `RedisClient` heeft verschillende extensies voor alle verschillende [Redis commando's](https://redis.io/commands).
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ Er is een bepaalde levenscyclus voor een abonnement:
|
|||
1. **message**: 0+ keer aangeroepen als berichten worden gepubliceerd in de geabonneerde kanalen
|
||||
1. **unsubscribe**: eenmaal aangeroepen wanneer het abonnement eindigt, hetzij door een verzoek, hetzij doordat de verbinding wordt verbroken
|
||||
|
||||
Wanneer je een abonnement aanmaakt, moet je minstens een [`messageReceiver`](https://swiftpackageindex.com/mordil/redistack/1.3.2/documentation/redistack/redissubscriptionmessagereceiver) voorzien om alle berichten te behandelen die gepubliceerd worden door het geabonneerde kanaal.
|
||||
Wanneer je een abonnement aanmaakt, moet je minstens een [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redissubscriptionmessagereceiver) voorzien om alle berichten te behandelen die gepubliceerd worden door het geabonneerde kanaal.
|
||||
|
||||
U kunt optioneel een `RedisSubscriptionChangeHandler` opgeven voor `onSubscribe` en `onUnsubscribe` om hun respectievelijke lifecycle events af te handelen.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[Redis](https://redis.io/) 是一种最流行的内存数据结构存储,通常用作缓存或消息代理。
|
||||
|
||||
这个库是 Vapor 和 [**RediStack**](https://gitlab.com/mordil/redistack) 的集成,它是与 Redis 通信的底层驱动程序。
|
||||
这个库是 Vapor 和 [**RediStack**](https://github.com/swift-server/RediStack) 的集成,它是与 Redis 通信的底层驱动程序。
|
||||
|
||||
!!! note "注意"
|
||||
Redis 的大部分功能都是由 **RediStack** 提供的。我们强烈建议你熟悉其文档。
|
||||
|
|
@ -32,7 +32,7 @@ targets: [
|
|||
|
||||
## 配置
|
||||
|
||||
Vapor 对 [`RedisConnection`](https://swiftpackageindex.com/mordil/redistack/1.3.2/documentation/redistack/redisconnection) 实例采用池化策略,并且有几个选项可以配置单个连接以及池本身。
|
||||
Vapor 对 [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisconnection) 实例采用池化策略,并且有几个选项可以配置单个连接以及池本身。
|
||||
|
||||
配置 Redis 的最低要求是提供一个 URL 来连接:
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ let serverAddresses: [SocketAddress] = [
|
|||
|
||||
## 发送命令
|
||||
|
||||
你可以使用 [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) 或 [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) 实例上的 `.redis` 属性发送命令,这使得你可以访问 [`RedisClient`](https://swiftpackageindex.com/mordil/redistack/1.3.2/documentation/redistack/redisclient)。
|
||||
你可以使用 [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) 或 [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) 实例上的 `.redis` 属性发送命令,这使得你可以访问 [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisclient)。
|
||||
|
||||
对于各别的 [Redis 命令](https://redis.io/commands),`RedisClient` 都有其对应的扩展。
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ Redis 支持进入[发布/订阅模式](https://redis.io/topics/pubsub),其中
|
|||
1. **message**:在消息发布到订阅频道时调用 0+ 次
|
||||
1. **unsubscribe**:订阅结束时调用一次,无论是通过请求还是连接丢失
|
||||
|
||||
创建订阅时,你必须至少提供一个 [`messageReceiver`](https://swiftpackageindex.com/mordil/redistack/1.3.2/documentation/redistack/redissubscriptionmessagereceiver) 来处理订阅频道发布的所有消息。
|
||||
创建订阅时,你必须至少提供一个 [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redissubscriptionmessagereceiver) 来处理订阅频道发布的所有消息。
|
||||
|
||||
你可以选择为 `onSubscribe` 和 `onUnsubscribe` 提供一个 `RedisSubscriptionChangeHandler` 来处理它们各自的生命周期事件。
|
||||
|
||||
|
|
|
|||
65
mkdocs.yml
65
mkdocs.yml
|
|
@ -102,6 +102,10 @@ plugins:
|
|||
name: Español
|
||||
site_name: Documentación de Vapor
|
||||
build: true
|
||||
pl:
|
||||
name: Polski
|
||||
site_name: Dokumentacja Vapor
|
||||
build: true
|
||||
# Add navigation translations here
|
||||
nav_translations:
|
||||
nl:
|
||||
|
|
@ -203,6 +207,7 @@ plugins:
|
|||
Deploy: Bereitstellung
|
||||
Contributing: Mitwirken
|
||||
Contributing Guide: Leitfaden für Beiträge
|
||||
Content: Modelbindung
|
||||
zh:
|
||||
Welcome: 序言
|
||||
Install: 安装
|
||||
|
|
@ -306,17 +311,17 @@ plugins:
|
|||
Install: Instalación
|
||||
Getting Started: Comenzando
|
||||
Hello, world: Hola, mundo
|
||||
Folder Structure: Estructura de carpetas
|
||||
Folder Structure: Estructura de Carpetas
|
||||
SwiftPM: SwiftPM
|
||||
Xcode: Xcode
|
||||
Basics: Fundamentos
|
||||
Routing: Routing
|
||||
Content: Content
|
||||
Client: Client
|
||||
Client: Cliente
|
||||
Validation: Validación
|
||||
Async: Async
|
||||
Logging: Logging
|
||||
Environment: Ambiente
|
||||
Environment: Entorno
|
||||
Errors: Errores
|
||||
Fluent: Fluent
|
||||
Overview: Presentación
|
||||
|
|
@ -347,11 +352,61 @@ plugins:
|
|||
Passwords: Contraseñas
|
||||
JWT: JWT
|
||||
Contributing: Colaborar
|
||||
Contributing Guide: Guía pra colaborar
|
||||
Contributing Guide: Guía para Colaborar
|
||||
Version (4.0): Versión (4.0)
|
||||
Legacy Docs: Documentación Legacy
|
||||
Upgrading: Actualizar
|
||||
|
||||
pl:
|
||||
Welcome: Witaj
|
||||
Install: Instalacja
|
||||
Getting Started: Jak zacząć
|
||||
Hello, world: Witaj, świecie
|
||||
Folder Structure: Struktura folderów
|
||||
SwiftPM: SwiftPM
|
||||
Xcode: Xcode
|
||||
Basics: Podstawy
|
||||
Routing: Kierowanie ruchem
|
||||
Content: Kontent
|
||||
Client: Klient
|
||||
Validation: Walidacja
|
||||
Async: Asynchroniczność
|
||||
Logging: Logowanie
|
||||
Environment: Środowisko
|
||||
Errors: Błędy
|
||||
Fluent: Fluent
|
||||
Overview: Prezentacja
|
||||
Model: Model
|
||||
Relations: Relacje
|
||||
Migrations: Migracje
|
||||
Query: Zapytania
|
||||
Transactions: Transakcje
|
||||
Schema: Schematy
|
||||
Leaf: Leaf
|
||||
Custom Tags: Własne tagi
|
||||
Redis: Redis
|
||||
Advanced: Zaawansowane
|
||||
Middleware: Middleware
|
||||
Testing: Testowanie
|
||||
Server: Serwer
|
||||
Files: Pliki
|
||||
Commands: Komendy
|
||||
Queues: Kolejki
|
||||
WebSockets: WebSockety
|
||||
Sessions: Sesje
|
||||
Services: Serwisy
|
||||
Security: Bezpieczeństwo
|
||||
APNS: APNS
|
||||
Deploy: Wdrożenie
|
||||
Authentication: Autentykacja
|
||||
Crypto: Kryptografia
|
||||
Passwords: Hasła
|
||||
JWT: JWT
|
||||
Contributing: Kontrybucja
|
||||
Contributing Guide: Przewodnik do kontrybucji
|
||||
Version (4.0): Wersja (4.0)
|
||||
Legacy Docs: Przestażała dokumentacja
|
||||
Upgrading: Aktualizacja
|
||||
Release Notes: Informacja o wersji
|
||||
nav:
|
||||
- Welcome: "index.md"
|
||||
- Install:
|
||||
|
|
|
|||
Loading…
Reference in New Issue