[sub-spm-optional-products] Add LCECryptoKit and LCFeatureControl as optional sub-SPM products
LCEssentials installs standalone; each sub target-depends on it so linking a sub's product always pulls LCEssentials in too, without the consumer having to declare it separately. - LCECryptoKit: internalized from the remote LCECryptoKitBinary git dependency (embedded token URL removed) into a local binaryTarget vendoring Frameworks/LCECryptoKit.xcframework. LCECryptoKitManager moved out of LCEssentials core into its own LCECryptoKitManager target/product; the no-op fallback for when the binary wasn't linked is gone (breaking change for existing consumers, see decisions/2026-09-15-sub-spm-optional-products.md). - LCFeatureControl: new product wrapping Atomenta's Feature Control API (flag evaluation with TTL cache + safe-degrade fallback to defaults, notifications inbox, batched exposure telemetry). 45 new tests.
This commit is contained in:
65
Sources/LCFeatureControl/FeatureControlNotification.swift
Normal file
65
Sources/LCFeatureControl/FeatureControlNotification.swift
Normal file
@@ -0,0 +1,65 @@
|
||||
import Foundation
|
||||
|
||||
/// `GET /api/feature-control/notifications` `status` query filter.
|
||||
public enum FeatureControlNotificationStatus: String, Sendable {
|
||||
case all, unread
|
||||
}
|
||||
|
||||
/// One item from `NotificationListItem`.
|
||||
public struct FeatureControlNotification: Sendable, Equatable, Identifiable {
|
||||
public let id: String
|
||||
public let title: String
|
||||
public let body: String
|
||||
public let severity: String
|
||||
public let createdAt: Date
|
||||
public let read: Bool
|
||||
public let ctaLabel: String?
|
||||
public let ctaUrl: String?
|
||||
|
||||
public init(id: String, title: String, body: String, severity: String,
|
||||
createdAt: Date, read: Bool, ctaLabel: String?, ctaUrl: String?) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.body = body
|
||||
self.severity = severity
|
||||
self.createdAt = createdAt
|
||||
self.read = read
|
||||
self.ctaLabel = ctaLabel
|
||||
self.ctaUrl = ctaUrl
|
||||
}
|
||||
}
|
||||
|
||||
extension FeatureControlNotification: Decodable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id, title, body, severity, createdAt, read, ctaLabel, ctaUrl
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let rawDate = try container.decode(String.self, forKey: .createdAt)
|
||||
guard let date = FeatureControlDateParsing.parse(rawDate) else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .createdAt, in: container,
|
||||
debugDescription: "Unrecognized date format: \(rawDate)")
|
||||
}
|
||||
self.id = try container.decode(String.self, forKey: .id)
|
||||
self.title = try container.decode(String.self, forKey: .title)
|
||||
self.body = try container.decode(String.self, forKey: .body)
|
||||
self.severity = try container.decode(String.self, forKey: .severity)
|
||||
self.createdAt = date
|
||||
self.read = try container.decode(Bool.self, forKey: .read)
|
||||
self.ctaLabel = try container.decodeIfPresent(String.self, forKey: .ctaLabel)
|
||||
self.ctaUrl = try container.decodeIfPresent(String.self, forKey: .ctaUrl)
|
||||
}
|
||||
}
|
||||
|
||||
/// `GET /api/feature-control/notifications` `result` shape.
|
||||
struct FeatureControlNotificationListResult: Decodable, Sendable {
|
||||
let items: [FeatureControlNotification]
|
||||
let nextCursor: String?
|
||||
}
|
||||
|
||||
/// Wire envelope for the notifications list — `{error, result}`.
|
||||
struct FeatureControlNotificationListEnvelope: Decodable, Sendable {
|
||||
let error: Bool
|
||||
let result: FeatureControlNotificationListResult
|
||||
}
|
||||
Reference in New Issue
Block a user