Skip to content

Commit 0690388

Browse files
author
Firefox Sync Engineering
committed
Nightly auto-update (122.0.20231128050327)
1 parent ba5a72c commit 0690388

File tree

11 files changed

+289
-113
lines changed

11 files changed

+289
-113
lines changed

Package.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
// swift-tools-version:5.4
22
import PackageDescription
33

4-
let checksum = "d78b70319ca98bdb0040c2802af1641ef5cdcb1231580e66b6dbf1819263c42f"
5-
let version = "122.0.20231123050305"
6-
let url = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.swift.122.20231123050305/artifacts/public/build/MozillaRustComponents.xcframework.zip"
4+
let checksum = "bce3fdcdcda71eb293131c901d63bb479146c0d04cc1231a4e058ed91dace5fc"
5+
let version = "122.0.20231128050327"
6+
let url = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.swift.122.20231128050327/artifacts/public/build/MozillaRustComponents.xcframework.zip"
77

88
// Focus xcframework
9-
let focusChecksum = "6e37ac908229e45fc47bfa8085c336b700d2617da97872a886d63dd44f0119b4"
10-
let focusUrl = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.swift.122.20231123050305/artifacts/public/build/FocusRustComponents.xcframework.zip"
9+
let focusChecksum = "103204e390127183bdca13bfc60973280df0cc2964781eb4ba8c79fb354f36bd"
10+
let focusUrl = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.swift.122.20231128050327/artifacts/public/build/FocusRustComponents.xcframework.zip"
1111
let package = Package(
1212
name: "MozillaRustComponentsSwift",
1313
platforms: [.iOS(.v14)],

swift-source/all/Generated/Metrics/Metrics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extension GleanMetrics {
2525
// Intentionally left private, no external user can instantiate a new global object.
2626
}
2727

28-
public static let info = BuildInfo(buildDate: DateComponents(calendar: Calendar.current, timeZone: TimeZone(abbreviation: "UTC"), year: 2023, month: 11, day: 23, hour: 5, minute: 17, second: 42))
28+
public static let info = BuildInfo(buildDate: DateComponents(calendar: Calendar.current, timeZone: TimeZone(abbreviation: "UTC"), year: 2023, month: 11, day: 28, hour: 5, minute: 34, second: 54))
2929
}
3030

3131
enum NimbusEvents {

swift-source/all/Nimbus/Bundle+.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public extension Array where Element == Bundle {
1414
func getImage(named name: String) -> UIImage? {
1515
for bundle in self {
1616
if let image = UIImage(named: name, in: bundle, compatibleWith: nil) {
17+
image.accessibilityIdentifier = name
1718
return image
1819
}
1920
}
@@ -78,3 +79,13 @@ public extension Bundle {
7879
return nil
7980
}
8081
}
82+
83+
public extension UIImage {
84+
/// The ``accessibilityIdentifier``, or "unknown-image" if not found.
85+
///
86+
/// The ``accessibilityIdentifier`` is set when images are loaded via Nimbus, so this
87+
/// really to make the compiler happy with the generated code.
88+
var encodableImageName: String {
89+
accessibilityIdentifier ?? "unknown-image"
90+
}
91+
}

swift-source/all/Nimbus/Collections+.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ public extension Dictionary {
1212
return [K1: Value](uniqueKeysWithValues: transformed)
1313
}
1414

15+
@inline(__always)
1516
func mapValuesNotNull<V1>(_ transform: (Value) -> V1?) -> [Key: V1] {
1617
return compactMapValues(transform)
1718
}
1819

19-
func mapNotNull<K1, V1>(_ keyTransform: (Key) -> K1?, _ valueTransform: (Value) -> V1?) -> [K1: V1] {
20+
func mapEntriesNotNull<K1, V1>(_ keyTransform: (Key) -> K1?, _ valueTransform: (Value) -> V1?) -> [K1: V1] {
2021
let transformed: [(K1, V1)] = compactMap { k, v in
2122
guard let k1 = keyTransform(k),
2223
let v1 = valueTransform(v)
@@ -37,6 +38,13 @@ public extension Dictionary {
3738
}
3839
}
3940

41+
public extension Array {
42+
@inline(__always)
43+
func mapNotNull<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
44+
try compactMap(transform)
45+
}
46+
}
47+
4048
/// Convenience extensions to make working elements coming from the `Variables`
4149
/// object slightly easier/regular.
4250
public extension String {

swift-source/all/Nimbus/FeatureHolder.swift

Lines changed: 117 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,47 @@ import Foundation
55

66
public typealias GetSdk = () -> FeaturesInterface?
77

8-
/// `FeatureHolder` is a class that unpacks a JSON object from the Nimbus SDK and transforms it into a useful
8+
public protocol FeatureHolderInterface {
9+
/// Send an exposure event for this feature. This should be done when the user is shown the feature, and may change
10+
/// their behavior because of it.
11+
func recordExposure()
12+
13+
/// Send an exposure event for this feature, in the given experiment.
14+
///
15+
/// If the experiment does not exist, or the client is not enrolled in that experiment, then no exposure event
16+
/// is recorded.
17+
///
18+
/// If you are not sure of the experiment slug, then this is _not_ the API you need: you should use
19+
/// {recordExposure} instead.
20+
///
21+
/// - Parameter slug the experiment identifier, likely derived from the ``value``.
22+
func recordExperimentExposure(slug: String)
23+
24+
/// Send a malformed feature event for this feature.
25+
///
26+
/// - Parameter partId an optional detail or part identifier to be attached to the event.
27+
func recordMalformedConfiguration(with partId: String)
28+
29+
/// Is this feature the focus of an automated test.
30+
///
31+
/// A utility flag to be used in conjunction with ``HardcodedNimbusFeatures``.
32+
///
33+
/// It is intended for use for app-code to detect when the app is under test, and
34+
/// take steps to make itself easier to test.
35+
///
36+
/// These cases should be rare, and developers should look for other ways to test
37+
/// code without relying on this facility.
38+
///
39+
/// For example, a background worker might be scheduled to run every 24 hours, but
40+
/// under test it would be desirable to run immediately, and only once.
41+
func isUnderTest() -> Bool
42+
}
43+
44+
/// ``FeatureHolder`` is a class that unpacks a JSON object from the Nimbus SDK and transforms it into a useful
945
/// type safe object, generated from a feature manifest (a `.fml.yaml` file).
1046
///
11-
/// The two routinely useful methods are the `value()` and `recordExposure()` events.
47+
/// The routinely useful methods to application developers are the ``value()`` and the event recording
48+
/// methods of ``FeatureHolderInterface``.
1249
///
1350
/// There are methods useful for testing, and more advanced uses: these all start with `with`.
1451
///
@@ -35,7 +72,7 @@ public class FeatureHolder<T: FMLFeatureInterface> {
3572
/// result used for the configuration of the feature.
3673
///
3774
/// Some care is taken to cache the value, this is for performance critical uses of the API.
38-
/// It is possible to invalidate the cache with `FxNimbus.invalidateCachedValues()` or `with(cachedValue: nil)`.
75+
/// It is possible to invalidate the cache with `FxNimbus.invalidateCachedValues()` or ``with(cachedValue: nil)``.
3976
public func value() -> T {
4077
lock.lock()
4178
defer { self.lock.unlock() }
@@ -53,48 +90,53 @@ public class FeatureHolder<T: FMLFeatureInterface> {
5390
return v
5491
}
5592

56-
/// Send an exposure event for this feature. This should be done when the user is shown the feature, and may change
57-
/// their behavior because of it.
93+
/// This overwrites the cached value with the passed one.
94+
///
95+
/// This is most likely useful during testing only.
96+
public func with(cachedValue value: T?) {
97+
lock.lock()
98+
defer { self.lock.unlock() }
99+
cachedValue = value
100+
}
101+
102+
/// This resets the SDK and clears the cached value.
103+
///
104+
/// This is especially useful at start up and for imported features.
105+
public func with(sdk: @escaping () -> FeaturesInterface?) {
106+
lock.lock()
107+
defer { self.lock.unlock() }
108+
getSdk = sdk
109+
cachedValue = nil
110+
}
111+
112+
/// This changes the mapping between a ``Variables`` and the feature configuration object.
113+
///
114+
/// This is most likely useful during testing and other generated code.
115+
public func with(initializer: @escaping (Variables, UserDefaults?) -> T) {
116+
lock.lock()
117+
defer { self.lock.unlock() }
118+
cachedValue = nil
119+
create = initializer
120+
}
121+
}
122+
123+
extension FeatureHolder: FeatureHolderInterface {
58124
public func recordExposure() {
59125
if !value().isModified() {
60126
getSdk()?.recordExposureEvent(featureId: featureId, experimentSlug: nil)
61127
}
62128
}
63129

64-
/// Send an exposure event for this feature, in the given experiment.
65-
///
66-
/// If the experiment does not exist, or the client is not enrolled in that experiment, then no exposure event
67-
/// is recorded.
68-
///
69-
/// If you are not sure of the experiment slug, then this is _not_ the API you need: you should use
70-
/// {recordExposure} instead.
71-
///
72-
/// - Parameter slug the experiment identifier, likely derived from the {value}.
73130
public func recordExperimentExposure(slug: String) {
74131
if !value().isModified() {
75132
getSdk()?.recordExposureEvent(featureId: featureId, experimentSlug: slug)
76133
}
77134
}
78135

79-
/// Send a malformed feature event for this feature.
80-
///
81-
/// - Parameter partId an optional detail or part identifier to be attached to the event.
82136
public func recordMalformedConfiguration(with partId: String = "") {
83137
getSdk()?.recordMalformedConfiguration(featureId: featureId, with: partId)
84138
}
85139

86-
/// Is this feature the focus of an automated test.
87-
///
88-
/// A utility flag to be used in conjunction with {HardcodedNimbusFeatures}.
89-
///
90-
/// It is intended for use for app-code to detect when the app is under test, and
91-
/// take steps to make itself easier to test.
92-
///
93-
/// These cases should be rare, and developers should look for other ways to test
94-
/// code without relying on this facility.
95-
///
96-
/// For example, a background worker might be scheduled to run every 24 hours, but
97-
/// under test it would be desirable to run immediately, and only once.
98140
public func isUnderTest() -> Bool {
99141
lock.lock()
100142
defer { self.lock.unlock() }
@@ -104,39 +146,53 @@ public class FeatureHolder<T: FMLFeatureInterface> {
104146
}
105147
return features.has(featureId: featureId)
106148
}
149+
}
107150

108-
/// This overwrites the cached value with the passed one.
109-
///
110-
/// This is most likely useful during testing only.
111-
public func with(cachedValue value: T?) {
112-
lock.lock()
113-
defer { self.lock.unlock() }
114-
cachedValue = value
151+
/// Swift generics don't allow us to do wildcards, which means implementing a
152+
/// ``getFeature(featureId: String) -> FeatureHolder<*>`` unviable.
153+
///
154+
/// To implement such a method, we need a wrapper object that gets the value, and forwards
155+
/// all other calls onto an inner ``FeatureHolder``.
156+
public class FeatureHolderAny {
157+
let inner: FeatureHolderInterface
158+
let innerValue: FMLFeatureInterface
159+
init<T>(wrapping holder: FeatureHolder<T>) {
160+
inner = holder
161+
innerValue = holder.value()
115162
}
116163

117-
/// This resets the SDK and clears the cached value.
118-
///
119-
/// This is especially useful at start up and for imported features.
120-
public func with(sdk: @escaping () -> FeaturesInterface?) {
121-
lock.lock()
122-
defer { self.lock.unlock() }
123-
getSdk = sdk
124-
cachedValue = nil
164+
public func value() -> FMLFeatureInterface {
165+
innerValue
125166
}
126167

127-
/// This changes the mapping between a `Variables` and the feature configuration object.
168+
/// Returns a JSON string representing the complete configuration.
128169
///
129-
/// This is most likely useful during testing and other generated code.
130-
public func with(initializer: @escaping (Variables, UserDefaults?) -> T) {
131-
lock.lock()
132-
defer { self.lock.unlock() }
133-
cachedValue = nil
134-
create = initializer
170+
/// A convenience for `self.value().toJSONString()`.
171+
public func toJSONString() -> String {
172+
innerValue.toJSONString()
173+
}
174+
}
175+
176+
extension FeatureHolderAny: FeatureHolderInterface {
177+
public func recordExposure() {
178+
inner.recordExposure()
179+
}
180+
181+
public func recordExperimentExposure(slug: String) {
182+
inner.recordExperimentExposure(slug: slug)
183+
}
184+
185+
public func recordMalformedConfiguration(with partId: String) {
186+
inner.recordMalformedConfiguration(with: partId)
187+
}
188+
189+
public func isUnderTest() -> Bool {
190+
inner.isUnderTest()
135191
}
136192
}
137193

138194
/// A bare-bones interface for the FML generated objects.
139-
public protocol FMLObjectInterface {}
195+
public protocol FMLObjectInterface: Encodable {}
140196

141197
/// A bare-bones interface for the FML generated features.
142198
///
@@ -150,10 +206,19 @@ public protocol FMLFeatureInterface: FMLObjectInterface {
150206
/// This may be `true` if a `pref-key` has been set in the feature manifest and the user has
151207
/// set that preference.
152208
func isModified() -> Bool
209+
210+
/// Returns a string representation of the complete feature configuration in JSON format.
211+
func toJSONString() -> String
153212
}
154213

155214
public extension FMLFeatureInterface {
156215
func isModified() -> Bool {
157216
return false
158217
}
218+
219+
func toJSONString() -> String {
220+
let encoder = JSONEncoder()
221+
let data = try! encoder.encode(self)
222+
return String(decoding: data, as: UTF8.self)
223+
}
159224
}

swift-source/all/Nimbus/FeatureManifestInterface.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,9 @@ public protocol FeatureManifestInterface {
3232
/// This happens automatically if you use the `NimbusBuilder` pattern of initialization.
3333
func invalidateCachedValues()
3434

35+
/// Get a feature configuration. This is of limited use for most uses of the FML, though
36+
/// is quite useful for introspection.
37+
func getFeature(featureId: String) -> FeatureHolderAny?
38+
3539
func getCoenrollingFeatureIds() -> [String]
3640
}

swift-source/focus/Generated/Metrics/Metrics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extension GleanMetrics {
2525
// Intentionally left private, no external user can instantiate a new global object.
2626
}
2727

28-
public static let info = BuildInfo(buildDate: DateComponents(calendar: Calendar.current, timeZone: TimeZone(abbreviation: "UTC"), year: 2023, month: 11, day: 23, hour: 5, minute: 17, second: 46))
28+
public static let info = BuildInfo(buildDate: DateComponents(calendar: Calendar.current, timeZone: TimeZone(abbreviation: "UTC"), year: 2023, month: 11, day: 28, hour: 5, minute: 34, second: 57))
2929
}
3030

3131
enum NimbusEvents {

swift-source/focus/Nimbus/Bundle+.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public extension Array where Element == Bundle {
1414
func getImage(named name: String) -> UIImage? {
1515
for bundle in self {
1616
if let image = UIImage(named: name, in: bundle, compatibleWith: nil) {
17+
image.accessibilityIdentifier = name
1718
return image
1819
}
1920
}
@@ -78,3 +79,13 @@ public extension Bundle {
7879
return nil
7980
}
8081
}
82+
83+
public extension UIImage {
84+
/// The ``accessibilityIdentifier``, or "unknown-image" if not found.
85+
///
86+
/// The ``accessibilityIdentifier`` is set when images are loaded via Nimbus, so this
87+
/// really to make the compiler happy with the generated code.
88+
var encodableImageName: String {
89+
accessibilityIdentifier ?? "unknown-image"
90+
}
91+
}

swift-source/focus/Nimbus/Collections+.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ public extension Dictionary {
1212
return [K1: Value](uniqueKeysWithValues: transformed)
1313
}
1414

15+
@inline(__always)
1516
func mapValuesNotNull<V1>(_ transform: (Value) -> V1?) -> [Key: V1] {
1617
return compactMapValues(transform)
1718
}
1819

19-
func mapNotNull<K1, V1>(_ keyTransform: (Key) -> K1?, _ valueTransform: (Value) -> V1?) -> [K1: V1] {
20+
func mapEntriesNotNull<K1, V1>(_ keyTransform: (Key) -> K1?, _ valueTransform: (Value) -> V1?) -> [K1: V1] {
2021
let transformed: [(K1, V1)] = compactMap { k, v in
2122
guard let k1 = keyTransform(k),
2223
let v1 = valueTransform(v)
@@ -37,6 +38,13 @@ public extension Dictionary {
3738
}
3839
}
3940

41+
public extension Array {
42+
@inline(__always)
43+
func mapNotNull<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
44+
try compactMap(transform)
45+
}
46+
}
47+
4048
/// Convenience extensions to make working elements coming from the `Variables`
4149
/// object slightly easier/regular.
4250
public extension String {

0 commit comments

Comments
 (0)