Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.10
// swift-tools-version:6.1
import PackageDescription

let package = Package(
Expand Down Expand Up @@ -31,7 +31,7 @@ let package = Package(
name: "LeafTests",
dependencies: [
.target(name: "Leaf"),
.product(name: "XCTVapor", package: "vapor"),
.product(name: "VaporTesting", package: "vapor"),
],
exclude: [
"Views",
Expand All @@ -43,8 +43,9 @@ let package = Package(

var swiftSettings: [SwiftSetting] { [
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("ConciseMagicFile"),
.enableUpcomingFeature("ForwardTrailingClosures"),
.enableUpcomingFeature("DisableOutwardActorInference"),
.enableExperimentalFeature("StrictConcurrency=complete"),
//.enableUpcomingFeature("InternalImportsByDefault"),
.enableUpcomingFeature("MemberImportVisibility"),
.enableUpcomingFeature("InferIsolatedConformances"),
//.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.enableUpcomingFeature("ImmutableWeakCaptures"),
] }
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<p align="center">
<div align="center">
<img src="https://design.vapor.codes/images/vapor-leaf.svg" height="96" alt="Leaf">
<br>
<br>
<a href="https://docs.vapor.codes/4.0/"><img src="https://design.vapor.codes/images/readthedocs.svg" alt="Documentation"></a>
<a href="https://discord.gg/vapor"><img src="https://design.vapor.codes/images/discordchat.svg" alt="Team Chat"></a>
<a href="LICENSE"><img src="https://design.vapor.codes/images/mitlicense.svg" alt="MIT License"></a>
<a href="https://github.com/vapor/leaf/actions/workflows/test.yml"><img src="https://img.shields.io/github/actions/workflow/status/vapor/leaf/test.yml?event=push&style=plastic&logo=github&label=tests&logoColor=%23ccc" alt="Continuous Integration"></a>
<a href="https://codecov.io/github/vapor/leaf"><img src="https://img.shields.io/codecov/c/github/vapor/leaf?style=plastic&logo=codecov&label=codecov"></a>
<a href="https://swift.org"><img src="https://design.vapor.codes/images/swift510up.svg" alt="Swift 5.10+"></a>
</p>

<br>
[![Documentation](https://design.vapor.codes/images/readthedocs.svg)](https://docs.vapor.codes/4.0/)
[![Team Chat](https://design.vapor.codes/images/discordchat.svg)](https://discord.gg/vapor)
[![MIT License](https://design.vapor.codes/images/mitlicense.svg)](./LICENSE)
[![Continuous Integration](https://img.shields.io/github/actions/workflow/status/vapor/leaf/test.yml?event=push&style=plastic&logo=github&label=tests&logoColor=ccc)](https://github.com/vapor/leaf/actions/workflows/test.yml)
[![Code Coverage](https://img.shields.io/codecov/c/github/vapor/leaf?style=plastic&logo=codecov&label=codecov)](https://codecov.io/github/vapor/leaf)
[![Swift 6.1+](https://design.vapor.codes/images/swift61up.svg)](https://swift.org)

</div>

Leaf provides integrations between [LeafKit](https://github.com/vapor/leaf-kit) and [Vapor](https://github.com/vapor/vapor) to make it easy to use Leaf templates in your Vapor app. It provides extensions to make it easy to set up, configure and use Leaf as your renderer in Vapor. It also conforms ``LeafRenderer`` to ``ViewRenderer`` so it can be used to render generic Views in Vapor.
Leaf provides integrations between [LeafKit](https://github.com/vapor/leaf-kit) and [Vapor](https://github.com/vapor/vapor) to make it easy to use Leaf templates in your Vapor app. It provides extensions to make it easy to set up, configure and use Leaf as your renderer in Vapor. It also conforms `LeafRenderer` to `ViewRenderer` so it can be used to render generic Views in Vapor.
11 changes: 6 additions & 5 deletions Sources/Leaf/Docs.docc/theme-settings.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
{
"theme": {
"aside": { "border-radius": "16px", "border-style": "double", "border-width": "3px" },
"aside": { "border-radius": "16px", "border-width": "3px", "border-style": "double" },
"border-radius": "0",
"button": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" },
"code": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" },
"color": {
"leaf": { "dark": "hsl(136, 43%, 53%)", "light": "hsl(136, 33%, 48%)" },
"documentation-intro-fill": "radial-gradient(circle at top, var(--color-leaf) 30%, #000 100%)",
"documentation-intro-fill": {
"dark": "radial-gradient(circle at top, var(--color-leaf) 0%, #000000 100%)",
"light": "radial-gradient(circle at top, var(--color-leaf) 0%, #f0f0f0 100%)"
},
"documentation-intro-accent": "var(--color-leaf)",
"documentation-intro-eyebrow": "white",
"documentation-intro-figure": "white",
"documentation-intro-title": "white",
"logo-base": { "dark": "#fff", "light": "#000" },
"logo-shape": { "dark": "#000", "light": "#fff" },
"fill": { "dark": "#000", "light": "#fff" }
},
"icons": { "technology": "/leaf/images/vapor-leaf-logo.svg" }
"icons": { "technology": "/leaf/images/Leaf/vapor-leaf-logo.svg" }
},
"features": {
"quickNavigation": { "enable": true },
Expand Down
148 changes: 130 additions & 18 deletions Sources/Leaf/LeafEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ extension LeafData: LeafEncodingResolvable {
}

extension LeafEncoder {
private protocol TransformableContainer {
func transform<NewKey: CodingKey>(to: NewKey.Type) -> KeyedContainerImpl<NewKey>
}

/// The ``Encoder`` conformer.
private final class EncoderImpl: Encoder, LeafEncodingResolvable, SingleValueEncodingContainer {
// See `Encoder.userinfo`.
Expand Down Expand Up @@ -93,11 +97,14 @@ extension LeafEncoder {
/// Need to expose the ability to access unwrapped keyed container to enable use of nested
/// keyed containers (see the keyed and unkeyed containers).
func rawContainer<Key: CodingKey>(keyedBy type: Key.Type) -> KeyedContainerImpl<Key> {
guard self.storage == nil else {
fatalError("Can't encode to multiple containers at the same encoding level")
if self.storage == nil {
self.storage = KeyedContainerImpl<Key>(encoder: self)
} else if let transformable = self.storage as? any TransformableContainer {
self.storage = transformable.transform(to: Key.self)
} else {
fatalError("Can't change container types at the same encoding level.")
}

self.storage = KeyedContainerImpl<Key>(encoder: self)
return self.storage as! KeyedContainerImpl<Key>
}

Expand All @@ -108,11 +115,14 @@ extension LeafEncoder {

// See `Encoder.unkeyedContainer()`.
func unkeyedContainer() -> any UnkeyedEncodingContainer {
guard self.storage == nil else {
fatalError("Can't encode to multiple containers at the same encoding level")
if self.storage == nil {
self.storage = UnkeyedContainerImpl(encoder: self)
} else {
guard self.storage! is UnkeyedContainerImpl else {
fatalError("Can't change container types at the same encoding level.")
}
}

self.storage = UnkeyedContainerImpl(encoder: self)
return self.storage as! UnkeyedContainerImpl
}

Expand All @@ -128,6 +138,35 @@ extension LeafEncoder {
// See `SingleValueEncodingContainer.encodeNil()`.
func encodeNil() throws {}

// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Bool) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: String) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Double) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Float) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Int) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Int8) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Int16) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Int32) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Int64) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: UInt) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: UInt8) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: UInt16) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: UInt32) throws { self.storage = try self.encode(value, forKey: nil) }
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: UInt64) throws { self.storage = try self.encode(value, forKey: nil) }

// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: some Encodable) throws {
self.storage = try self.encode(value, forKey: nil)
Expand All @@ -150,20 +189,38 @@ extension LeafEncoder {
}
}

private final class KeyedContainerImpl<Key>: KeyedEncodingContainerProtocol, LeafEncodingResolvable where Key: CodingKey {
private final class RefWrapper<T> {
var value: T

init(_ value: T) {
self.value = value
}
}

private final class KeyedContainerImpl<Key>: KeyedEncodingContainerProtocol, LeafEncodingResolvable, TransformableContainer where Key: CodingKey {
private unowned let encoder: EncoderImpl
private var data: [String: any LeafEncodingResolvable] = [:]
private var nestedEncoderCaptures: [AnyObject] = []
private var data: RefWrapper<[String: any LeafEncodingResolvable]> = .init([:])
private var nestedEncoderCaptures: RefWrapper<[AnyObject]> = .init([])

// See `LeafEncodingResolvable.resolvedData`.
var resolvedData: LeafData? {
.dictionary(self.data.compactMapValues { $0.resolvedData })
.dictionary(self.data.value.compactMapValues { $0.resolvedData })
}

init(encoder: EncoderImpl) {
self.encoder = encoder
}

private init(encoder: EncoderImpl, data: RefWrapper<[String: any LeafEncodingResolvable]>, nestedEncoderCaptures: RefWrapper<[AnyObject]>) {
self.encoder = encoder
self.data = data
self.nestedEncoderCaptures = nestedEncoderCaptures
}

func transform<NewKey: CodingKey>(to: NewKey.Type = NewKey.self) -> KeyedContainerImpl<NewKey> {
.init(encoder: self.encoder, data: self.data, nestedEncoderCaptures: self.nestedEncoderCaptures)
}

// See `KeyedEncodingContainerProtocol.codingPath`.
var codingPath: [any CodingKey] {
self.encoder.codingPath
Expand All @@ -172,20 +229,49 @@ extension LeafEncoder {
// See `KeyedEncodingContainerProtocol.encodeNil()`.
func encodeNil(forKey key: Key) throws {}

// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: Bool, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: String, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: Double, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: Float, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: Int, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: Int8, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: Int16, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: Int32, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: Int64, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: UInt, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: UInt8, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: UInt16, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: UInt32, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }
// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: UInt64, forKey key: Key) throws { (try? self.encoder.encode(value, forKey: key))?.map { self.data.value[key.stringValue] = $0 } }

// See `KeyedEncodingContainerProtocol.encode(_:forKey:)`.
func encode(_ value: some Encodable, forKey key: Key) throws {
guard let encodedValue = try self.encoder.encode(value, forKey: key) else {
return
}

self.data[key.stringValue] = encodedValue
self.data.value[key.stringValue] = encodedValue
}

// See `KeyedEncodingContainerProtocol.nestedContainer(keyedBy:forKey:)`.
func nestedContainer<NestedKey: CodingKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
let nestedEncoder = EncoderImpl(from: self.encoder, withKey: key)

self.nestedEncoderCaptures.append(nestedEncoder)
self.nestedEncoderCaptures.value.append(nestedEncoder)

/// Use a subencoder to create a nested container so the coding paths are correctly maintained.
/// Save the subcontainer in our data so it can be resolved later before returning it.
Expand All @@ -200,7 +286,7 @@ extension LeafEncoder {
func nestedUnkeyedContainer(forKey key: Key) -> any UnkeyedEncodingContainer {
let nestedEncoder = EncoderImpl(from: self.encoder, withKey: key)

self.nestedEncoderCaptures.append(nestedEncoder)
self.nestedEncoderCaptures.value.append(nestedEncoder)

return self.insert(
nestedEncoder.unkeyedContainer() as! UnkeyedContainerImpl,
Expand All @@ -224,7 +310,7 @@ extension LeafEncoder {

/// Helper for the encoding methods.
private func insert<T>(_ value: any LeafEncodingResolvable, forKey key: any CodingKey, as: T.Type = T.self) -> T {
self.data[key.stringValue] = value
self.data.value[key.stringValue] = value
return value as! T
}
}
Expand Down Expand Up @@ -257,11 +343,37 @@ extension LeafEncoder {
func encodeNil() throws {}

// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: some Encodable) throws {
guard let encodedValue = try self.encoder.encode(value, forKey: self.nextCodingKey) else {
return
}
func encode(_ value: Bool) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: String) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: Double) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: Float) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: Int) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: Int8) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: Int16) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: Int32) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: Int64) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: UInt) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: UInt8) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: UInt16) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: UInt32) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }
// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: UInt64) throws { (try? self.encoder.encode(value, forKey: self.nextCodingKey))?.map { self.data.append($0) } }

// See `UnkeyedEncodingContainer.encode(_:)`.
func encode(_ value: some Encodable) throws {
guard let encodedValue = try self.encoder.encode(value, forKey: self.nextCodingKey) else { return }
self.data.append(encodedValue)
}

Expand Down
Loading
Loading