From 229822756b434e8328a4710f270d15197525f6d7 Mon Sep 17 00:00:00 2001 From: Rachel McRoberts Date: Tue, 30 May 2023 14:29:44 +0100 Subject: [PATCH 1/5] Add fallback when SKU field value is a number --- Networking/Networking/Model/Product/Product.swift | 7 ++++++- Networking/NetworkingTests/Mapper/ProductMapperTests.swift | 1 + .../Responses/product-alternative-types.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Networking/Networking/Model/Product/Product.swift b/Networking/Networking/Model/Product/Product.swift index 7c325a749ec..1d719f9abb9 100644 --- a/Networking/Networking/Model/Product/Product.swift +++ b/Networking/Networking/Model/Product/Product.swift @@ -357,7 +357,12 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable let fullDescription = try container.decodeIfPresent(String.self, forKey: .fullDescription) let shortDescription = try container.decodeIfPresent(String.self, forKey: .shortDescription) - let sku = try container.decodeIfPresent(String.self, forKey: .sku) + + // Even though a plain install of WooCommerce Core provides String values, + // some plugins alter the field value from String to Int or Decimal. + let sku = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .sku, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) // Even though a plain install of WooCommerce Core provides string values, // some plugins alter the field value from String to Int or Decimal. diff --git a/Networking/NetworkingTests/Mapper/ProductMapperTests.swift b/Networking/NetworkingTests/Mapper/ProductMapperTests.swift index 22ba41eb71a..8c4032b33ea 100644 --- a/Networking/NetworkingTests/Mapper/ProductMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/ProductMapperTests.swift @@ -117,6 +117,7 @@ final class ProductMapperTests: XCTestCase { XCTAssertFalse(product.soldIndividually) XCTAssertTrue(product.purchasable) XCTAssertEqual(product.permalink, "") + XCTAssertEqual(product.sku, "123") } /// Verifies that the `salePrice` field of the Product are parsed correctly when the product is on sale, and the sale price is an empty string diff --git a/Networking/NetworkingTests/Responses/product-alternative-types.json b/Networking/NetworkingTests/Responses/product-alternative-types.json index 08f6d14fba5..262ae3b886e 100644 --- a/Networking/NetworkingTests/Responses/product-alternative-types.json +++ b/Networking/NetworkingTests/Responses/product-alternative-types.json @@ -14,7 +14,7 @@ "catalog_visibility": "visible", "description": "

This is the party room!

\n", "short_description": "[contact-form]\n

The green room’s max capacity is 30 people. Reserving the date / time of your event is free. We can also accommodate large groups, with seating for 85 board game players at a time. If you have a large group, let us know and we’ll send you our large group rate.

\n

GROUP RATES

\n

Reserve your event for up to 30 guests for $100.

\n", - "sku": "", + "sku": 123, "price": 17, "regular_price": 12.89, "sale_price": 26.73, From 7d9192dc8c21b5999de8666525460739aa7c2569 Mon Sep 17 00:00:00 2001 From: Rachel McRoberts Date: Tue, 30 May 2023 14:32:37 +0100 Subject: [PATCH 2/5] Add fallback when weight field value is a number --- Networking/Networking/Model/Product/Product.swift | 7 ++++++- Networking/NetworkingTests/Mapper/ProductMapperTests.swift | 1 + .../Responses/product-alternative-types.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Networking/Networking/Model/Product/Product.swift b/Networking/Networking/Model/Product/Product.swift index 1d719f9abb9..c6ac90d2489 100644 --- a/Networking/Networking/Model/Product/Product.swift +++ b/Networking/Networking/Model/Product/Product.swift @@ -429,7 +429,12 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable let backordered = try container.decode(Bool.self, forKey: .backordered) let soldIndividually = try container.decodeIfPresent(Bool.self, forKey: .soldIndividually) ?? false - let weight = try container.decodeIfPresent(String.self, forKey: .weight) + + // Even though a plain install of WooCommerce Core provides String values, + // some plugins alter the field value from String to Int or Decimal. + let weight = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .weight, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) let dimensions = try container.decode(ProductDimensions.self, forKey: .dimensions) let shippingRequired = try container.decode(Bool.self, forKey: .shippingRequired) diff --git a/Networking/NetworkingTests/Mapper/ProductMapperTests.swift b/Networking/NetworkingTests/Mapper/ProductMapperTests.swift index 8c4032b33ea..0a0533a9f2b 100644 --- a/Networking/NetworkingTests/Mapper/ProductMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/ProductMapperTests.swift @@ -118,6 +118,7 @@ final class ProductMapperTests: XCTestCase { XCTAssertTrue(product.purchasable) XCTAssertEqual(product.permalink, "") XCTAssertEqual(product.sku, "123") + XCTAssertEqual(product.weight, "213") } /// Verifies that the `salePrice` field of the Product are parsed correctly when the product is on sale, and the sale price is an empty string diff --git a/Networking/NetworkingTests/Responses/product-alternative-types.json b/Networking/NetworkingTests/Responses/product-alternative-types.json index 262ae3b886e..f35f26cd08b 100644 --- a/Networking/NetworkingTests/Responses/product-alternative-types.json +++ b/Networking/NetworkingTests/Responses/product-alternative-types.json @@ -42,7 +42,7 @@ "backorders_allowed": false, "backordered": false, "sold_individually": null, - "weight": "213", + "weight": 213, "dimensions": { "length": "12", "width": "33", From b7a6775e6fbf657393657cf794894b6c6c00165a Mon Sep 17 00:00:00 2001 From: Rachel McRoberts Date: Tue, 30 May 2023 14:44:18 +0100 Subject: [PATCH 3/5] Add fallbacks when product dimension field values are a number --- .../Model/Product/ProductDimensions.swift | 18 ++++++++++++++++++ .../Mapper/ProductMapperTests.swift | 3 +++ .../Responses/product-alternative-types.json | 6 +++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Networking/Networking/Model/Product/ProductDimensions.swift b/Networking/Networking/Model/Product/ProductDimensions.swift index 9999b92d016..e861c194bef 100644 --- a/Networking/Networking/Model/Product/ProductDimensions.swift +++ b/Networking/Networking/Model/Product/ProductDimensions.swift @@ -17,6 +17,24 @@ public struct ProductDimensions: Codable, Equatable, GeneratedFakeable { self.width = width self.height = height } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + // Even though a plain install of WooCommerce Core provides String dimension values, + // some plugins may alter the field values from String to Int or Decimal. + let length = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .length, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) ?? "" + let width = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .width, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) ?? "" + let height = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .height, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) ?? "" + + self.init(length: length, width: width, height: height) + } } /// Defines all of the Dimensions CodingKeys diff --git a/Networking/NetworkingTests/Mapper/ProductMapperTests.swift b/Networking/NetworkingTests/Mapper/ProductMapperTests.swift index 0a0533a9f2b..100866f25c7 100644 --- a/Networking/NetworkingTests/Mapper/ProductMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/ProductMapperTests.swift @@ -119,6 +119,9 @@ final class ProductMapperTests: XCTestCase { XCTAssertEqual(product.permalink, "") XCTAssertEqual(product.sku, "123") XCTAssertEqual(product.weight, "213") + XCTAssertEqual(product.dimensions.length, "12") + XCTAssertEqual(product.dimensions.width, "33") + XCTAssertEqual(product.dimensions.height, "54") } /// Verifies that the `salePrice` field of the Product are parsed correctly when the product is on sale, and the sale price is an empty string diff --git a/Networking/NetworkingTests/Responses/product-alternative-types.json b/Networking/NetworkingTests/Responses/product-alternative-types.json index f35f26cd08b..fd5f950bb23 100644 --- a/Networking/NetworkingTests/Responses/product-alternative-types.json +++ b/Networking/NetworkingTests/Responses/product-alternative-types.json @@ -44,9 +44,9 @@ "sold_individually": null, "weight": 213, "dimensions": { - "length": "12", - "width": "33", - "height": "54" + "length": 12, + "width": 33, + "height": 54 }, "shipping_required": false, "shipping_taxable": false, From 1a48a6dcaa7b6b73d0a4e4afe0422029fb03b9e5 Mon Sep 17 00:00:00 2001 From: Rachel McRoberts Date: Tue, 30 May 2023 14:52:48 +0100 Subject: [PATCH 4/5] Add fallback when product download ID is a number --- Networking/Networking/Model/Product/ProductDownload.swift | 6 +++++- Networking/NetworkingTests/Mapper/ProductMapperTests.swift | 1 + .../Responses/product-alternative-types.json | 6 +++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Networking/Networking/Model/Product/ProductDownload.swift b/Networking/Networking/Model/Product/ProductDownload.swift index 73c116db1e2..09378c2ad5d 100644 --- a/Networking/Networking/Model/Product/ProductDownload.swift +++ b/Networking/Networking/Model/Product/ProductDownload.swift @@ -23,7 +23,11 @@ public struct ProductDownload: Codable, Equatable, GeneratedFakeable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let downloadID = try container.decode(String.self, forKey: .downloadID) + // Even though a plain install of WooCommerce Core provides String values, + // some plugins alter the field value from String to Int or Decimal. + let downloadID = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .downloadID, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) ?? "0" let name = try container.decodeIfPresent(String.self, forKey: .name) let fileURL = try container.decodeIfPresent(String.self, forKey: .fileURL) diff --git a/Networking/NetworkingTests/Mapper/ProductMapperTests.swift b/Networking/NetworkingTests/Mapper/ProductMapperTests.swift index 100866f25c7..563f2f6b70c 100644 --- a/Networking/NetworkingTests/Mapper/ProductMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/ProductMapperTests.swift @@ -122,6 +122,7 @@ final class ProductMapperTests: XCTestCase { XCTAssertEqual(product.dimensions.length, "12") XCTAssertEqual(product.dimensions.width, "33") XCTAssertEqual(product.dimensions.height, "54") + XCTAssertEqual(product.downloads.first?.downloadID, "12345") } /// Verifies that the `salePrice` field of the Product are parsed correctly when the product is on sale, and the sale price is an empty string diff --git a/Networking/NetworkingTests/Responses/product-alternative-types.json b/Networking/NetworkingTests/Responses/product-alternative-types.json index fd5f950bb23..dfad347304f 100644 --- a/Networking/NetworkingTests/Responses/product-alternative-types.json +++ b/Networking/NetworkingTests/Responses/product-alternative-types.json @@ -28,7 +28,11 @@ "total_sales": 0, "virtual": true, "downloadable": false, - "downloads": [], + "downloads": [{ + "id" : 12345, + "name" : "Song #1", + "file" : "https://example.com/woo-single-1.ogg" + }], "download_limit": -1, "download_expiry": -1, "external_url": "http://somewhere.com", From 87a9b009115c729cbacaff17c7f58877dbda6928 Mon Sep 17 00:00:00 2001 From: Rachel McRoberts Date: Tue, 30 May 2023 16:17:35 +0100 Subject: [PATCH 5/5] Add product decoding fallbacks to 13.9 release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index e4e3bb9b543..06e38896aa9 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,6 +3,7 @@ 13.9 ----- - [*] Payments: Location permissions request is not shown to TTP users who grant "Allow once" permission on first foregrounding the app any more [https://github.com/woocommerce/woocommerce-ios/pull/9821] +- [*] Products: Allow alternative types for the `sku` and `weight` in `Product`, the dimensions in `ProductDimensions`, and the `downloadID` in `ProductDownload`, as some third-party plugins alter the types in the API. This helps with the product list not loading due to product decoding errors. [https://github.com/woocommerce/woocommerce-ios/pull/9846] 13.8 -----