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
11 changes: 10 additions & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ let package = Package(
#if os(macOS) || os(Linux)
result.append(.package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "4.119.0")))
result.append(.package(url: "https://github.com/vapor/fluent-kit.git", .upToNextMajor(from: "1.52.2")))
result.append(.package(url: "https://github.com/vapor/leaf-kit.git", .upToNextMajor(from: "1.11.0")))
#endif

return result
Expand Down Expand Up @@ -175,6 +176,7 @@ let package = Package(
.byName(name: "FOSMacros"),
.product(name: "Vapor", package: "Vapor"),
.product(name: "FluentKit", package: "fluent-kit"),
.product(name: "LeafKit", package: "leaf-kit"),
.product(name: "Yams", package: "Yams")
]
))
Expand Down
1 change: 0 additions & 1 deletion Sources/FOSMVVM/Localization/LocalizedProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,6 @@ public extension RetrievablePropertyNames {
self.vFirst = vFirst ?? .vInitial
self.vLast = vLast
}

}

public extension _LocalizedProperty {
Expand Down
2 changes: 0 additions & 2 deletions Sources/FOSMVVM/Protocols/ServerRequest+Fetch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ public extension ServerRequest {
return responseBody
}

#if canImport(SwiftUI)
/// Send the ``ServerRequest`` to the web service and wait for a response
///
/// Upon receipt of a response from the server, ``responseBody`` will be updated with
Expand Down Expand Up @@ -131,7 +130,6 @@ public extension ServerRequest {
}
}
}
#endif
}

public enum ServerRequestProcessingError: Error, CustomDebugStringConvertible {
Expand Down
2 changes: 0 additions & 2 deletions Sources/FOSMVVM/SwiftUI Support/MVVMEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,6 @@ public final class MVVMEnvironment: @unchecked Sendable {
self.deploymentURLs = deploymentURLs
self.requestErrorHandler = requestErrorHandler


#if canImport(SwiftUI)
self.loadingView = { AnyView(DefaultLoadingView()) }
let currentVersion = currentVersion ?? (try? appBundle.appleOSVersion) ?? SystemVersion.current
Expand All @@ -326,7 +325,6 @@ public final class MVVMEnvironment: @unchecked Sendable {
let currentVersion = currentVersion ?? SystemVersion.current
SystemVersion.setCurrentVersion(currentVersion)
#endif

}

/// Initializes the ``MVVMEnvironment`` for non-SwiftUI Applications
Expand Down
2 changes: 1 addition & 1 deletion Sources/FOSMVVM/SwiftUI Support/MVVMEnvirontmentView.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// MVVMEnvirontmentView.swift
//
// Copyright 2025 FOS Computer Services, LLC
// Copyright 2024 FOS Computer Services, LLC
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion Sources/FOSMVVM/SwiftUI Support/Tab.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Tab.swift
//
// Copyright 2025 FOS Computer Services, LLC
// Copyright 2024 FOS Computer Services, LLC
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion Sources/FOSMVVM/SwiftUI Support/Text.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Text.swift
//
// Copyright 2025 FOS Computer Services, LLC
// Copyright 2024 FOS Computer Services, LLC
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion Sources/FOSMVVM/SwiftUI Support/ViewModelView.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// ViewModelView.swift
//
// Copyright 2025 FOS Computer Services, LLC
// Copyright 2024 FOS Computer Services, LLC
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand Down
4 changes: 2 additions & 2 deletions Sources/FOSMVVMVapor/Extensions/Application+FOS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ public extension Application {
}

func initMVVMEnvironment(_ mvvmEnvironment: MVVMEnvironment) async throws {
lifecycle.use(MVVMEnvironmentInitializer(
try await lifecycle.use(MVVMEnvironmentInitializer(
mvvmEnvironment: mvvmEnvironment,
serverBaseURL: try await mvvmEnvironment.serverBaseURL
serverBaseURL: mvvmEnvironment.serverBaseURL
))
}
}
Expand Down
62 changes: 62 additions & 0 deletions Sources/FOSMVVMVapor/Extensions/Localizable+Leaf.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Localizable+Leaf.swift
//
// Copyright 2025 FOS Computer Services, LLC
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import FOSMVVM
import LeafKit

// Extends `Localizable` types to render as their `localizedString` in Leaf templates
//
// Without this extension, Localizable types that encode as keyed containers
// (like `LocalizableDate`) would render as their debug description instead of
// the localized string value.

// MARK: - Concrete Conformances

extension LocalizableString: LeafDataRepresentable {
public var leafData: LeafData {
.string((try? localizedString) ?? "")
}
}

extension LocalizableDate: LeafDataRepresentable {
public var leafData: LeafData {
.string((try? localizedString) ?? "")
}
}

extension LocalizableInt: LeafDataRepresentable {
public var leafData: LeafData {
.string((try? localizedString) ?? "")
}
}

extension LocalizableArray: LeafDataRepresentable {
public var leafData: LeafData {
.string((try? localizedString) ?? "")
}
}

extension LocalizableCompoundValue: LeafDataRepresentable {
public var leafData: LeafData {
.string((try? localizedString) ?? "")
}
}

extension LocalizableSubstitutions: LeafDataRepresentable {
public var leafData: LeafData {
.string((try? localizedString) ?? "")
}
}
2 changes: 1 addition & 1 deletion Sources/FOSMVVMVapor/Extensions/String+Pluralize.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ extension String {
private func pluralizeByRules() -> String {
guard !isEmpty else { return self }

let lowercased = self.lowercased()
let lowercased = lowercased()

// Words ending in -s, -ss, -x, -z, -ch, -sh → add "es"
if lowercased.hasSuffix("ss") ||
Expand Down
92 changes: 92 additions & 0 deletions Sources/FOSMVVMVapor/Vapor Support/UpdateController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// UpdateController.swift
//
// Copyright 2025 FOS Computer Services, LLC
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import FOSFoundation
import FOSMVVM
import Vapor

public protocol ServerRequestController: AnyObject, ControllerRouting, RouteCollection {
associatedtype TRequest: ServerRequest

typealias ActionProcessor = (
Vapor.Request,
TRequest,
TRequest.RequestBody
) async throws -> TRequest.ResponseBody

var actions: [ServerRequestAction: ActionProcessor] { get }
}

// MARK: Default Implementation

public extension ServerRequestController {
static var baseURL: String { TRequest.path }

func boot(routes: RoutesBuilder) throws {
let groupName = Self.baseURL == "/"
? ""
: Self.baseURL

let routeGroup = routes
.grouped(.constant(groupName))

for pair in actions {
switch pair.key {
case .create: routeGroup.post { try await Self.run($0, processor: pair.value) }
case .replace: routeGroup.put { try await Self.run($0, processor: pair.value) }
case .update: routeGroup.patch { try await Self.run($0, processor: pair.value) }
default: throw ServerRequestControllerError.invalidAction(pair.key)
}
}
}
}

public enum ServerRequestControllerError: Error, CustomDebugStringConvertible {
case invalidAction(ServerRequestAction)
case missingRequestBody

public var debugDescription: String {
switch self {
case .invalidAction(let action):
"Invalid ServerRequestAction: \(action). Only create, replace and update are allowed."
case .missingRequestBody:
"Server request was missing its request body."
}
}
}

// MARK: Private Methods

private extension ServerRequestController {
static func run(_ req: Vapor.Request, processor: ActionProcessor) async throws -> Vapor.Response {
let requestBody: TRequest.RequestBody = if TRequest.RequestBody.self == EmptyBody.self {
// swiftlint:disable:next force_cast
(EmptyBody() as! TRequest.RequestBody)
} else {
try req.content.decode(TRequest.RequestBody.self)
}

let serverRequest = TRequest(
query: nil,
fragment: nil,
requestBody: requestBody,
responseBody: nil
)

return try await processor(req, serverRequest, requestBody)
.buildResponse(req)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// VaporServerRequestHost.swift
//
// Copyright 2025 FOS Computer Services, LLC
// Copyright 2024 FOS Computer Services, LLC
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// VaporServerRequestMiddleware.swift
//
// Copyright 2025 FOS Computer Services, LLC
// Copyright 2024 FOS Computer Services, LLC
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion Sources/FOSMVVMVapor/Vapor Support/ViewModelRequest.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// ViewModelRequest.swift
//
// Copyright 2025 FOS Computer Services, LLC
// Copyright 2024 FOS Computer Services, LLC
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand Down