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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

[Full changelog](https://github.com/mozilla/glean/compare/v66.0.0...main)

* Swift
* Rely on `Codable` again for serialization of object metrics ([#3300](https://github.com/mozilla/glean/pull/3300))

# v66.0.0 (2025-10-20)

[Full changelog](https://github.com/mozilla/glean/compare/v65.2.2...v66.0.0)
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion glean-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ include = [
rust-version = "1.82"

[package.metadata.glean]
glean-parser = "18.0.2"
glean-parser = "18.0.6"

[badges]
circle-ci = { repository = "mozilla/glean", branch = "main" }
Expand Down
2 changes: 1 addition & 1 deletion glean-core/build/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "glean-build"
version = "18.0.2"
version = "18.0.6"
edition = "2021"
description = "Glean SDK Rust build helper"
repository = "https://github.com/mozilla/glean"
Expand Down
2 changes: 1 addition & 1 deletion glean-core/build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use std::{env, path::PathBuf};

use xshell_venv::{Result, Shell, VirtualEnv};

const GLEAN_PARSER_VERSION: &str = "18.0.2";
const GLEAN_PARSER_VERSION: &str = "18.0.6";

/// A Glean Rust bindings generator.
pub struct Builder {
Expand Down
32 changes: 2 additions & 30 deletions glean-core/ios/Glean/Metrics/ObjectMetric.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,11 @@
/// An object that can be serialized into JSON.
///
/// Objects are defined by their structure in the metrics definition.
public protocol ObjectSerialize: Decodable {
public protocol ObjectSerialize: Codable {
func intoSerializedObject() -> String
}

extension Array: ObjectSerialize where Element: ObjectSerialize {
public func intoSerializedObject() -> String {
var json = "["
var first = true
for elem in self {
if !first {
json.append(",")
}
first = false
json.append(elem.intoSerializedObject())
}
json.append("]")
return json
}
}

extension String: ObjectSerialize {
extension Array: ObjectSerialize where Element: Codable {
public func intoSerializedObject() -> String {
let jsonEncoder = JSONEncoder()
let jsonData = try! jsonEncoder.encode(self)
Expand All @@ -34,18 +18,6 @@ extension String: ObjectSerialize {
}
}

extension Bool: ObjectSerialize {
public func intoSerializedObject() -> String {
return self ? "true" : "false"
}
}

extension Int64: ObjectSerialize {
public func intoSerializedObject() -> String {
return String(self)
}
}

/// This implements the developer facing API for the object metric type.
///
/// Instances of this class type are automatically generated by the parsers at built time,
Expand Down
132 changes: 117 additions & 15 deletions glean-core/ios/GleanTests/Metrics/ObjectMetricTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,82 @@
@testable import Glean
import XCTest

struct BalloonsObjectItem: Decodable, Equatable, ObjectSerialize {
struct BalloonsObjectItem: Codable, Equatable {
var colour: String?
var diameter: Int64?
var anotherValue: Bool?

func intoSerializedObject() -> String {
var data: [String] = []
if let val = self.colour {
var elem = "\"colour\":"
elem.append(val.intoSerializedObject())
data.append(elem)
enum CodingKeys: String, CodingKey {
case colour = "colour"
case diameter = "diameter"
case anotherValue = "another_value"
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if let colour = self.colour {
try container.encode(colour, forKey: .colour)
}
if let val = self.diameter {
var elem = "\"diameter\":"
elem.append(val.intoSerializedObject())
data.append(elem)
if let diameter = self.diameter {
try container.encode(diameter, forKey: .diameter)
}
if let anotherValue = self.anotherValue {
try container.encode(anotherValue, forKey: .anotherValue)
}
let obj = data.joined(separator: ",")
let json = "{" + obj + "}"
return json
}
}

typealias BalloonsObject = [BalloonsObjectItem]

// generated from
//
// ```
// structure:
// type: object
// properties:
// key1:
// type: string
// another_value:
// type: number
// sub_array:
// type: array
// items:
// type: number
// ```
struct ToplevelObjectObject: Codable, Equatable, ObjectSerialize {
var key1: String?
var anotherValue: Int64?
var subArray: ToplevelObjectObjectSubArray = []

enum CodingKeys: String, CodingKey {
case key1 = "key1"
case anotherValue = "another_value"
case subArray = "sub_array"
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if let key1 = self.key1 {
try container.encode(key1, forKey: .key1)
}
if let anotherValue = self.anotherValue {
try container.encode(anotherValue, forKey: .anotherValue)
}
if subArray.count > 0 {
let subArray = self.subArray
try container.encode(subArray, forKey: .subArray)
}
}

func intoSerializedObject() -> String {
let jsonEncoder = JSONEncoder()
let jsonData = try! jsonEncoder.encode(self)
let json = String(data: jsonData, encoding: String.Encoding.utf8)!
return json
}
}
typealias ToplevelObjectObjectSubArray = [ToplevelObjectObjectSubArrayItem]
typealias ToplevelObjectObjectSubArrayItem = Int64

class ObjectMetricTypeTests: XCTestCase {
override func setUp() {
resetGleanDiscardingInitialPings(testCase: self, tag: "ObjectMetricTypeTests")
Expand Down Expand Up @@ -132,4 +184,54 @@ class ObjectMetricTypeTests: XCTestCase {
XCTAssertEqual(2, snapshot.count)
XCTAssertEqual(expected, snapshot)
}

func testObjectDecodesFromSnakeCase() {
let metric = ObjectMetricType<BalloonsObject>(CommonMetricData(
category: "test",
name: "balloon",
sendInPings: ["store1"],
lifetime: .ping,
disabled: false
))

XCTAssertNil(metric.testGetValue())

var balloons: BalloonsObject = []
balloons.append(BalloonsObjectItem(colour: "red", diameter: 5, anotherValue: true))
balloons.append(BalloonsObjectItem(colour: "green", anotherValue: false))
metric.set(balloons)

let snapshot = metric.testGetValue()!
XCTAssertNotNil(snapshot)
XCTAssertEqual(2, snapshot.count)

XCTAssertEqual(snapshot[0].colour, "red")
XCTAssertEqual(snapshot[0].diameter, 5)
XCTAssertEqual(snapshot[0].anotherValue, true)
XCTAssertEqual(snapshot[1].colour, "green")
XCTAssertNil(snapshot[1].diameter)
XCTAssertEqual(snapshot[1].anotherValue, false)
}

func testObjectWithStructureOnToplevel() {
let metric = ObjectMetricType<ToplevelObjectObject>(CommonMetricData(
category: "test",
name: "toplevel_object",
sendInPings: ["store1"],
lifetime: .ping,
disabled: false
))

XCTAssertNil(metric.testGetValue())

let obj = ToplevelObjectObject(key1: "test", anotherValue: 3, subArray: [1, 2, 3])
metric.set(obj)

let snapshot = metric.testGetValue()!
XCTAssertNotNil(snapshot)

XCTAssertEqual("test", snapshot.key1)
XCTAssertEqual(3, snapshot.anotherValue)
XCTAssertEqual([1, 2, 3], snapshot.subArray)
}
}
2 changes: 1 addition & 1 deletion glean-core/python/glean/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
__email__ = "glean-team@mozilla.com"


GLEAN_PARSER_VERSION = "18.0.2"
GLEAN_PARSER_VERSION = "18.0.6"
parser_version = VersionInfo.parse(GLEAN_PARSER_VERSION)
parser_version_next_major = parser_version.bump_major()

Expand Down
2 changes: 2 additions & 0 deletions samples/ios/app/glean-sample-app/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
glean.initialize(uploadEnabled: true, buildInfo: GleanMetrics.GleanBuild.info)
}

glean.setDebugViewTag("jer-ios")

Test.timespan.start()

// Set a sample value for a metric.
Expand Down
3 changes: 3 additions & 0 deletions samples/ios/app/glean-sample-app/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ class ViewController: UIViewController {
ch.append(f)
Party.chooser.set(ch)

let tlObj = Party.ToplevelObjectObject(key1: "test", anotherValue: 3, subArray: [1, 2, 3])
Party.toplevelObject.set(tlObj)

// This is referencing the event ping named 'click' from the metrics.yaml file. In
// order to illustrate adding extra information to the event, it is also adding to the
// 'extras' field a dictionary of values. Note that the dictionary keys must be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ class ViewControllerTest: XCTestCase {

let chooser = objects["party.chooser"] as! [[String: AnyHashable]]
XCTAssertEqual(expectedChooser, chooser)

let obj = objects["party.toplevel_object"] as! [String: AnyHashable]
let expectedObj: [String: AnyHashable] = [
"key1": "test",
"another_value": 3,
"sub_array": [1, 2, 3]
]
XCTAssertEqual(expectedObj, obj)
}

func testViewControllerInteraction() {
Expand Down
24 changes: 24 additions & 0 deletions samples/ios/app/metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,27 @@ party:
- type: string
- type: number
- type: boolean

toplevel_object:
type: object
description: Top-level object
bugs:
- https://bugzilla.mozilla.org/11137353
data_reviews:
- http://example.com/reviews
notification_emails:
- CHANGE-ME@example.com
expires: never
send_in_pings:
- sample
structure:
type: object
properties:
key1:
type: string
another_value:
type: number
sub_array:
type: array
items:
type: number