Hermes Agent 36967370fd SwiftBricks: Bricks JSON UI framework native Swift/SwiftUI implementation
- Core: Schema (Codable), Store, EventBus, RPC, Engine, I18n
- Controls: Text, Title1-6, Label, Input, Textarea, Number, Date, Button, Select
  VBox, HBox, ScrollPanel, DynamicColumn, Tabular, Form, InlineForm,
  TabView, Menu, PopupWindow, Html, Image, UrlWidget
- Renderer: BricksView (main entry), ControlRenderer (recursive dispatch)
- 30+ widget types, 9 bind actiontypes
- Form validation (required/minlength/maxlength/min/max/email/number/pattern)
- i18n with multi-language JSON files
- 18 tests covering schema parsing, store, i18n, events
2026-06-21 12:12:03 +08:00

183 lines
5.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
/// RPC Bricksbricks.tget()fetch
/// API
@MainActor
public final class BricksRPC: ObservableObject {
public struct Response: Sendable {
public let data: Data
public let statusCode: Int
public let headers: [String: String]
public var json: Any? {
try? JSONSerialization.jsonObject(with: data)
}
public var dictionary: [String: Any]? {
json as? [String: Any]
}
public var array: [[String: Any]]? {
if let dict = dictionary, let rows = dict["rows"] as? [[String: Any]] {
return rows
}
return json as? [[String: Any]]
}
public var text: String {
String(data: data, encoding: .utf8) ?? ""
}
public var isSuccess: Bool {
(200...299).contains(statusCode)
}
}
/// URL
public var baseURL: String = ""
///
public var defaultHeaders: [String: String] = [
"Content-Type": "application/json"
]
/// token
public var authToken: String?
///
public var requestInterceptor: ((URLRequest) -> URLRequest)?
///
@Published public var activeRequests: Int = 0
private let session: URLSession
public init(session: URLSession = .shared) {
self.session = session
}
// MARK: - GET
public func get(_ url: String, params: [String: String]? = nil) async throws -> Response {
var urlString = resolveURL(url)
if let params, !params.isEmpty {
let query = params.map { "\($0.key)=\($0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? $0.value)" }
.joined(separator: "&")
urlString += (urlString.contains("?") ? "&" : "?") + query
}
return try await request(urlString, method: "GET")
}
// MARK: - POST
public func post(_ url: String, body: [String: Any]? = nil) async throws -> Response {
var data: Data?
if let body {
data = try JSONSerialization.data(withJSONObject: body)
}
return try await request(resolveURL(url), method: "POST", body: data)
}
public func postForm(_ url: String, fields: [String: String]) async throws -> Response {
let formString = fields.map { "\($0.key)=\($0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? $0.value)" }
.joined(separator: "&")
let data = formString.data(using: .utf8)
return try await request(resolveURL(url), method: "POST", body: data,
headers: ["Content-Type": "application/x-www-form-urlencoded"])
}
// MARK: -
public func request(_ url: String, method: String = "GET", body: Data? = nil,
headers: [String: String]? = nil) async throws -> Response {
activeRequests += 1
defer { activeRequests -= 1 }
guard let urlObj = URL(string: url) else {
throw BricksRPCError.invalidURL(url)
}
var request = URLRequest(url: urlObj)
request.httpMethod = method
request.httpBody = body
request.timeoutInterval = 30
//
for (key, value) in defaultHeaders {
request.setValue(value, forHTTPHeaderField: key)
}
//
if let headers {
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
}
//
if let token = authToken {
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
//
if let interceptor = requestInterceptor {
request = interceptor(request)
}
do {
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw BricksRPCError.invalidResponse
}
let responseHeaders = Dictionary(
uniqueKeysWithValues: httpResponse.allHeaderFields.compactMap { key, value in
guard let keyStr = key as? String, let valStr = value as? String else { return nil }
return (keyStr, valStr)
}
)
return Response(
data: data,
statusCode: httpResponse.statusCode,
headers: responseHeaders
)
} catch {
throw BricksRPCError.networkError(error)
}
}
// MARK: - URL
private func resolveURL(_ url: String) -> String {
if url.hasPrefix("http://") || url.hasPrefix("https://") {
return url
}
if baseURL.isEmpty {
return url
}
let base = baseURL.hasSuffix("/") ? String(baseURL.dropLast()) : baseURL
let path = url.hasPrefix("/") ? url : "/\(url)"
return base + path
}
}
// MARK: -
public enum BricksRPCError: Error, LocalizedError {
case invalidURL(String)
case invalidResponse
case networkError(Error)
case httpError(Int, String)
public var errorDescription: String? {
switch self {
case .invalidURL(let url): return "无效URL: \(url)"
case .invalidResponse: return "无效响应"
case .networkError(let error): return "网络错误: \(error.localizedDescription)"
case .httpError(let code, let msg): return "HTTP \(code): \(msg)"
}
}
}