From ba1bee57b71ec56af7a8c85514a9c26e2d8d5ab1 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Wed, 13 May 2026 11:29:02 +0100 Subject: [PATCH] Make generated JSON helpers checked Sendable --- .../swift/api/ShopifyCheckoutProtocol.json | 542 ++++++++++++++++-- .../Generated/Models.swift | 268 +-------- .../ShopifyCheckoutProtocol/JSONAny.swift | 302 ++++++++++ protocol/scripts/generate_models.mjs | 38 +- 4 files changed, 838 insertions(+), 312 deletions(-) create mode 100644 protocol/languages/swift/Sources/ShopifyCheckoutProtocol/JSONAny.swift diff --git a/platforms/swift/api/ShopifyCheckoutProtocol.json b/platforms/swift/api/ShopifyCheckoutProtocol.json index 786e479b..f40a1f54 100644 --- a/platforms/swift/api/ShopifyCheckoutProtocol.json +++ b/platforms/swift/api/ShopifyCheckoutProtocol.json @@ -1341,6 +1341,96 @@ "name": "Checkout", "printedName": "Checkout", "children": [ + { + "kind": "Var", + "name": "attribution", + "printedName": "attribution", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "s:23ShopifyCheckoutProtocol0B0V11attributionSDyS2SGSgvp", + "mangledName": "$s23ShopifyCheckoutProtocol0B0V11attributionSDyS2SGSgvp", + "moduleName": "ShopifyCheckoutProtocol", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "s:23ShopifyCheckoutProtocol0B0V11attributionSDyS2SGSgvg", + "mangledName": "$s23ShopifyCheckoutProtocol0B0V11attributionSDyS2SGSgvg", + "moduleName": "ShopifyCheckoutProtocol", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, { "kind": "Var", "name": "buyer", @@ -2352,6 +2442,43 @@ "name": "CodingKeys", "printedName": "CodingKeys", "children": [ + { + "kind": "Var", + "name": "attribution", + "printedName": "attribution", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(ShopifyCheckoutProtocol.Checkout.CodingKeys.Type) -> ShopifyCheckoutProtocol.Checkout.CodingKeys", + "children": [ + { + "kind": "TypeNominal", + "name": "CodingKeys", + "printedName": "ShopifyCheckoutProtocol.Checkout.CodingKeys", + "usr": "s:23ShopifyCheckoutProtocol0B0V10CodingKeysO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "ShopifyCheckoutProtocol.Checkout.CodingKeys.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "CodingKeys", + "printedName": "ShopifyCheckoutProtocol.Checkout.CodingKeys", + "usr": "s:23ShopifyCheckoutProtocol0B0V10CodingKeysO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:23ShopifyCheckoutProtocol0B0V10CodingKeysO11attributionyA2EmF", + "mangledName": "$s23ShopifyCheckoutProtocol0B0V10CodingKeysO11attributionyA2EmF", + "moduleName": "ShopifyCheckoutProtocol" + }, { "kind": "Var", "name": "buyer", @@ -3335,7 +3462,7 @@ { "kind": "Constructor", "name": "init", - "printedName": "init(buyer:context:continueURL:currency:discounts:expiresAt:fulfillment:id:lineItems:links:messages:order:payment:signals:status:totals:ucp:)", + "printedName": "init(attribution:buyer:context:continueURL:currency:discounts:expiresAt:fulfillment:id:lineItems:links:messages:order:payment:signals:status:totals:ucp:)", "children": [ { "kind": "TypeNominal", @@ -3343,6 +3470,34 @@ "printedName": "ShopifyCheckoutProtocol.Checkout", "usr": "s:23ShopifyCheckoutProtocol0B0V" }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, { "kind": "TypeNominal", "name": "Optional", @@ -3559,8 +3714,8 @@ } ], "declKind": "Constructor", - "usr": "s:23ShopifyCheckoutProtocol0B0V5buyer7context11continueURL8currency9discounts9expiresAt11fulfillment2id9lineItems5links8messages5order7payment7signals6status6totals3ucpAcA5BuyerVSg_AA7ContextVSgSSSgSSAA0B9DiscountsVSg10Foundation4DateVSgAA0B11FulfillmentVSgSSSayAA8LineItemVGSayAA4LinkVGSayAA7MessageVGSgAA17OrderConfirmationVSgAA7PaymentVSgAA7SignalsVSgAA0B6StatusOSayAA0B5TotalVGAA25UCPCheckoutResponseSchemaVtcfc", - "mangledName": "$s23ShopifyCheckoutProtocol0B0V5buyer7context11continueURL8currency9discounts9expiresAt11fulfillment2id9lineItems5links8messages5order7payment7signals6status6totals3ucpAcA5BuyerVSg_AA7ContextVSgSSSgSSAA0B9DiscountsVSg10Foundation4DateVSgAA0B11FulfillmentVSgSSSayAA8LineItemVGSayAA4LinkVGSayAA7MessageVGSgAA17OrderConfirmationVSgAA7PaymentVSgAA7SignalsVSgAA0B6StatusOSayAA0B5TotalVGAA25UCPCheckoutResponseSchemaVtcfc", + "usr": "s:23ShopifyCheckoutProtocol0B0V11attribution5buyer7context11continueURL8currency9discounts9expiresAt11fulfillment2id9lineItems5links8messages5order7payment7signals6status6totals3ucpACSDyS2SGSg_AA5BuyerVSgAA7ContextVSgSSSgSSAA0B9DiscountsVSg10Foundation4DateVSgAA0B11FulfillmentVSgSSSayAA8LineItemVGSayAA4LinkVGSayAA7MessageVGSgAA17OrderConfirmationVSgAA7PaymentVSgAA7SignalsVSgAA0B6StatusOSayAA0B5TotalVGAA25UCPCheckoutResponseSchemaVtcfc", + "mangledName": "$s23ShopifyCheckoutProtocol0B0V11attribution5buyer7context11continueURL8currency9discounts9expiresAt11fulfillment2id9lineItems5links8messages5order7payment7signals6status6totals3ucpACSDyS2SGSg_AA5BuyerVSgAA7ContextVSgSSSgSSAA0B9DiscountsVSg10Foundation4DateVSgAA0B11FulfillmentVSgSSSayAA8LineItemVGSayAA4LinkVGSayAA7MessageVGSgAA17OrderConfirmationVSgAA7PaymentVSgAA7SignalsVSgAA0B6StatusOSayAA0B5TotalVGAA25UCPCheckoutResponseSchemaVtcfc", "moduleName": "ShopifyCheckoutProtocol", "init_kind": "Designated" }, @@ -3703,7 +3858,7 @@ { "kind": "Function", "name": "with", - "printedName": "with(buyer:context:continueURL:currency:discounts:expiresAt:fulfillment:id:lineItems:links:messages:order:payment:signals:status:totals:ucp:)", + "printedName": "with(attribution:buyer:context:continueURL:currency:discounts:expiresAt:fulfillment:id:lineItems:links:messages:order:payment:signals:status:totals:ucp:)", "children": [ { "kind": "TypeNominal", @@ -3711,6 +3866,43 @@ "printedName": "ShopifyCheckoutProtocol.Checkout", "usr": "s:23ShopifyCheckoutProtocol0B0V" }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]??", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + }, { "kind": "TypeNominal", "name": "Optional", @@ -4080,8 +4272,8 @@ } ], "declKind": "Func", - "usr": "s:23ShopifyCheckoutProtocol0B0V4with5buyer7context11continueURL8currency9discounts9expiresAt11fulfillment2id9lineItems5links8messages5order7payment7signals6status6totals3ucpAcA5BuyerVSgSg_AA7ContextVSgSgSSSgSgA2_AA0B9DiscountsVSgSg10Foundation4DateVSgSgAA0B11FulfillmentVSgSgA2_SayAA8LineItemVGSgSayAA4LinkVGSgSayAA7MessageVGSgSgAA17OrderConfirmationVSgSgAA7PaymentVSgSgAA7SignalsVSgSgAA0B6StatusOSgSayAA0B5TotalVGSgAA25UCPCheckoutResponseSchemaVSgtF", - "mangledName": "$s23ShopifyCheckoutProtocol0B0V4with5buyer7context11continueURL8currency9discounts9expiresAt11fulfillment2id9lineItems5links8messages5order7payment7signals6status6totals3ucpAcA5BuyerVSgSg_AA7ContextVSgSgSSSgSgA2_AA0B9DiscountsVSgSg10Foundation4DateVSgSgAA0B11FulfillmentVSgSgA2_SayAA8LineItemVGSgSayAA4LinkVGSgSayAA7MessageVGSgSgAA17OrderConfirmationVSgSgAA7PaymentVSgSgAA7SignalsVSgSgAA0B6StatusOSgSayAA0B5TotalVGSgAA25UCPCheckoutResponseSchemaVSgtF", + "usr": "s:23ShopifyCheckoutProtocol0B0V4with11attribution5buyer7context11continueURL8currency9discounts9expiresAt11fulfillment2id9lineItems5links8messages5order7payment7signals6status6totals3ucpACSDyS2SGSgSg_AA5BuyerVSgSgAA7ContextVSgSgSSSgSgA6_AA0B9DiscountsVSgSg10Foundation4DateVSgSgAA0B11FulfillmentVSgSgA6_SayAA8LineItemVGSgSayAA4LinkVGSgSayAA7MessageVGSgSgAA17OrderConfirmationVSgSgAA7PaymentVSgSgAA7SignalsVSgSgAA0B6StatusOSgSayAA0B5TotalVGSgAA25UCPCheckoutResponseSchemaVSgtF", + "mangledName": "$s23ShopifyCheckoutProtocol0B0V4with11attribution5buyer7context11continueURL8currency9discounts9expiresAt11fulfillment2id9lineItems5links8messages5order7payment7signals6status6totals3ucpACSDyS2SGSgSg_AA5BuyerVSgSgAA7ContextVSgSgSSSgSgA6_AA0B9DiscountsVSgSg10Foundation4DateVSgSgAA0B11FulfillmentVSgSgA6_SayAA8LineItemVGSgSayAA4LinkVGSgSayAA7MessageVGSgSgAA17OrderConfirmationVSgSgAA7PaymentVSgSgAA7SignalsVSgSgAA0B6StatusOSgSayAA0B5TotalVGSgAA25UCPCheckoutResponseSchemaVSgtF", "moduleName": "ShopifyCheckoutProtocol", "isFromExtension": true, "funcSelfKind": "NonMutating" @@ -40603,6 +40795,96 @@ } ] }, + { + "kind": "Var", + "name": "attribution", + "printedName": "attribution", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "s:23ShopifyCheckoutProtocol5OrderV11attributionSDyS2SGSgvp", + "mangledName": "$s23ShopifyCheckoutProtocol5OrderV11attributionSDyS2SGSgvp", + "moduleName": "ShopifyCheckoutProtocol", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "s:23ShopifyCheckoutProtocol5OrderV11attributionSDyS2SGSgvg", + "mangledName": "$s23ShopifyCheckoutProtocol5OrderV11attributionSDyS2SGSgvg", + "moduleName": "ShopifyCheckoutProtocol", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, { "kind": "Var", "name": "checkoutID", @@ -41185,6 +41467,43 @@ "mangledName": "$s23ShopifyCheckoutProtocol5OrderV10CodingKeysO11adjustmentsyA2EmF", "moduleName": "ShopifyCheckoutProtocol" }, + { + "kind": "Var", + "name": "attribution", + "printedName": "attribution", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(ShopifyCheckoutProtocol.Order.CodingKeys.Type) -> ShopifyCheckoutProtocol.Order.CodingKeys", + "children": [ + { + "kind": "TypeNominal", + "name": "CodingKeys", + "printedName": "ShopifyCheckoutProtocol.Order.CodingKeys", + "usr": "s:23ShopifyCheckoutProtocol5OrderV10CodingKeysO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "ShopifyCheckoutProtocol.Order.CodingKeys.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "CodingKeys", + "printedName": "ShopifyCheckoutProtocol.Order.CodingKeys", + "usr": "s:23ShopifyCheckoutProtocol5OrderV10CodingKeysO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:23ShopifyCheckoutProtocol5OrderV10CodingKeysO11attributionyA2EmF", + "mangledName": "$s23ShopifyCheckoutProtocol5OrderV10CodingKeysO11attributionyA2EmF", + "moduleName": "ShopifyCheckoutProtocol" + }, { "kind": "Var", "name": "checkoutID", @@ -41909,7 +42228,7 @@ { "kind": "Constructor", "name": "init", - "printedName": "init(adjustments:checkoutID:currency:fulfillment:id:label:lineItems:messages:permalinkURL:totals:ucp:)", + "printedName": "init(adjustments:attribution:checkoutID:currency:fulfillment:id:label:lineItems:messages:permalinkURL:totals:ucp:)", "children": [ { "kind": "TypeNominal", @@ -41939,6 +42258,34 @@ ], "usr": "s:Sq" }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, { "kind": "TypeNominal", "name": "String", @@ -42041,8 +42388,8 @@ } ], "declKind": "Constructor", - "usr": "s:23ShopifyCheckoutProtocol5OrderV11adjustments10checkoutID8currency11fulfillment2id5label9lineItems8messages12permalinkURL6totals3ucpACSayAA10AdjustmentVGSg_S2SAA11FulfillmentVS2SSgSayAA0D8LineItemVGSayAA7MessageVGSgSSSayAA0B5TotalVGAA22UCPOrderResponseSchemaVtcfc", - "mangledName": "$s23ShopifyCheckoutProtocol5OrderV11adjustments10checkoutID8currency11fulfillment2id5label9lineItems8messages12permalinkURL6totals3ucpACSayAA10AdjustmentVGSg_S2SAA11FulfillmentVS2SSgSayAA0D8LineItemVGSayAA7MessageVGSgSSSayAA0B5TotalVGAA22UCPOrderResponseSchemaVtcfc", + "usr": "s:23ShopifyCheckoutProtocol5OrderV11adjustments11attribution10checkoutID8currency11fulfillment2id5label9lineItems8messages12permalinkURL6totals3ucpACSayAA10AdjustmentVGSg_SDyS2SGSgS2SAA11FulfillmentVS2SSgSayAA0D8LineItemVGSayAA7MessageVGSgSSSayAA0B5TotalVGAA22UCPOrderResponseSchemaVtcfc", + "mangledName": "$s23ShopifyCheckoutProtocol5OrderV11adjustments11attribution10checkoutID8currency11fulfillment2id5label9lineItems8messages12permalinkURL6totals3ucpACSayAA10AdjustmentVGSg_SDyS2SGSgS2SAA11FulfillmentVS2SSgSayAA0D8LineItemVGSayAA7MessageVGSgSSSayAA0B5TotalVGAA22UCPOrderResponseSchemaVtcfc", "moduleName": "ShopifyCheckoutProtocol", "init_kind": "Designated" }, @@ -42185,7 +42532,7 @@ { "kind": "Function", "name": "with", - "printedName": "with(adjustments:checkoutID:currency:fulfillment:id:label:lineItems:messages:permalinkURL:totals:ucp:)", + "printedName": "with(adjustments:attribution:checkoutID:currency:fulfillment:id:label:lineItems:messages:permalinkURL:totals:ucp:)", "children": [ { "kind": "TypeNominal", @@ -42224,6 +42571,43 @@ "hasDefaultArg": true, "usr": "s:Sq" }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]??", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + }, { "kind": "TypeNominal", "name": "Optional", @@ -42416,8 +42800,8 @@ } ], "declKind": "Func", - "usr": "s:23ShopifyCheckoutProtocol5OrderV4with11adjustments10checkoutID8currency11fulfillment2id5label9lineItems8messages12permalinkURL6totals3ucpACSayAA10AdjustmentVGSgSg_SSSgAuA11FulfillmentVSgA2USgSayAA0D8LineItemVGSgSayAA7MessageVGSgSgAUSayAA0B5TotalVGSgAA22UCPOrderResponseSchemaVSgtF", - "mangledName": "$s23ShopifyCheckoutProtocol5OrderV4with11adjustments10checkoutID8currency11fulfillment2id5label9lineItems8messages12permalinkURL6totals3ucpACSayAA10AdjustmentVGSgSg_SSSgAuA11FulfillmentVSgA2USgSayAA0D8LineItemVGSgSayAA7MessageVGSgSgAUSayAA0B5TotalVGSgAA22UCPOrderResponseSchemaVSgtF", + "usr": "s:23ShopifyCheckoutProtocol5OrderV4with11adjustments11attribution10checkoutID8currency11fulfillment2id5label9lineItems8messages12permalinkURL6totals3ucpACSayAA10AdjustmentVGSgSg_SDyS2SGSgSgSSSgAyA11FulfillmentVSgA2YSgSayAA0D8LineItemVGSgSayAA7MessageVGSgSgAYSayAA0B5TotalVGSgAA22UCPOrderResponseSchemaVSgtF", + "mangledName": "$s23ShopifyCheckoutProtocol5OrderV4with11adjustments11attribution10checkoutID8currency11fulfillment2id5label9lineItems8messages12permalinkURL6totals3ucpACSayAA10AdjustmentVGSgSg_SDyS2SGSgSgSSSgAyA11FulfillmentVSgA2YSgSayAA0D8LineItemVGSgSayAA7MessageVGSgSgAYSayAA0B5TotalVGSgAA22UCPOrderResponseSchemaVSgtF", "moduleName": "ShopifyCheckoutProtocol", "isFromExtension": true, "funcSelfKind": "NonMutating" @@ -66404,43 +66788,6 @@ ], "funcSelfKind": "NonMutating" }, - { - "kind": "Var", - "name": "hashValue", - "printedName": "hashValue", - "children": [ - { - "kind": "TypeNominal", - "name": "Int", - "printedName": "Swift.Int", - "usr": "s:Si" - } - ], - "declKind": "Var", - "usr": "s:23ShopifyCheckoutProtocol8JSONNullC9hashValueSivp", - "mangledName": "$s23ShopifyCheckoutProtocol8JSONNullC9hashValueSivp", - "moduleName": "ShopifyCheckoutProtocol", - "accessors": [ - { - "kind": "Accessor", - "name": "Get", - "printedName": "Get()", - "children": [ - { - "kind": "TypeNominal", - "name": "Int", - "printedName": "Swift.Int", - "usr": "s:Si" - } - ], - "declKind": "Accessor", - "usr": "s:23ShopifyCheckoutProtocol8JSONNullC9hashValueSivg", - "mangledName": "$s23ShopifyCheckoutProtocol8JSONNullC9hashValueSivg", - "moduleName": "ShopifyCheckoutProtocol", - "accessorKind": "get" - } - ] - }, { "kind": "Function", "name": "hash", @@ -66463,6 +66810,9 @@ "usr": "s:23ShopifyCheckoutProtocol8JSONNullC4hash4intoys6HasherVz_tF", "mangledName": "$s23ShopifyCheckoutProtocol8JSONNullC4hash4intoys6HasherVz_tF", "moduleName": "ShopifyCheckoutProtocol", + "declAttributes": [ + "Final" + ], "funcSelfKind": "NonMutating" }, { @@ -66532,14 +66882,65 @@ "usr": "s:23ShopifyCheckoutProtocol8JSONNullC6encode2toys7Encoder_p_tKF", "mangledName": "$s23ShopifyCheckoutProtocol8JSONNullC6encode2toys7Encoder_p_tKF", "moduleName": "ShopifyCheckoutProtocol", + "declAttributes": [ + "Final" + ], "throwing": true, "funcSelfKind": "NonMutating" + }, + { + "kind": "Var", + "name": "hashValue", + "printedName": "hashValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:23ShopifyCheckoutProtocol8JSONNullC9hashValueSivp", + "mangledName": "$s23ShopifyCheckoutProtocol8JSONNullC9hashValueSivp", + "moduleName": "ShopifyCheckoutProtocol", + "implicit": true, + "declAttributes": [ + "Final" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:23ShopifyCheckoutProtocol8JSONNullC9hashValueSivg", + "mangledName": "$s23ShopifyCheckoutProtocol8JSONNullC9hashValueSivg", + "moduleName": "ShopifyCheckoutProtocol", + "implicit": true, + "declAttributes": [ + "Final" + ], + "accessorKind": "get" + } + ] } ], "declKind": "Class", "usr": "s:23ShopifyCheckoutProtocol8JSONNullC", "mangledName": "$s23ShopifyCheckoutProtocol8JSONNullC", "moduleName": "ShopifyCheckoutProtocol", + "declAttributes": [ + "Final" + ], "conformances": [ { "kind": "Conformance", @@ -66562,6 +66963,13 @@ "usr": "s:SH", "mangledName": "$sSH" }, + { + "kind": "Conformance", + "name": "Sendable", + "printedName": "Sendable", + "usr": "s:s8SendableP", + "mangledName": "$ss8SendableP" + }, { "kind": "Conformance", "name": "Equatable", @@ -66569,6 +66977,13 @@ "usr": "s:SQ", "mangledName": "$sSQ" }, + { + "kind": "Conformance", + "name": "SendableMetatype", + "printedName": "SendableMetatype", + "usr": "s:s16SendableMetatypeP", + "mangledName": "$ss16SendableMetatypeP" + }, { "kind": "Conformance", "name": "Copyable", @@ -66606,11 +67021,8 @@ "mangledName": "$s23ShopifyCheckoutProtocol7JSONAnyC5valueypvp", "moduleName": "ShopifyCheckoutProtocol", "declAttributes": [ - "Final", - "HasStorage" + "Final" ], - "isLet": true, - "hasStorage": true, "accessors": [ { "kind": "Accessor", @@ -66627,10 +67039,8 @@ "usr": "s:23ShopifyCheckoutProtocol7JSONAnyC5valueypvg", "mangledName": "$s23ShopifyCheckoutProtocol7JSONAnyC5valueypvg", "moduleName": "ShopifyCheckoutProtocol", - "implicit": true, "declAttributes": [ - "Final", - "Transparent" + "Final" ], "accessorKind": "get" } @@ -66685,6 +67095,9 @@ "usr": "s:23ShopifyCheckoutProtocol7JSONAnyC6encode2toys7Encoder_p_tKF", "mangledName": "$s23ShopifyCheckoutProtocol7JSONAnyC6encode2toys7Encoder_p_tKF", "moduleName": "ShopifyCheckoutProtocol", + "declAttributes": [ + "Final" + ], "throwing": true, "funcSelfKind": "NonMutating" } @@ -66693,6 +67106,9 @@ "usr": "s:23ShopifyCheckoutProtocol7JSONAnyC", "mangledName": "$s23ShopifyCheckoutProtocol7JSONAnyC", "moduleName": "ShopifyCheckoutProtocol", + "declAttributes": [ + "Final" + ], "conformances": [ { "kind": "Conformance", @@ -66708,6 +67124,20 @@ "usr": "s:SE", "mangledName": "$sSE" }, + { + "kind": "Conformance", + "name": "Sendable", + "printedName": "Sendable", + "usr": "s:s8SendableP", + "mangledName": "$ss8SendableP" + }, + { + "kind": "Conformance", + "name": "SendableMetatype", + "printedName": "SendableMetatype", + "usr": "s:s16SendableMetatypeP", + "mangledName": "$ss16SendableMetatypeP" + }, { "kind": "Conformance", "name": "Copyable", diff --git a/protocol/languages/swift/Sources/ShopifyCheckoutProtocol/Generated/Models.swift b/protocol/languages/swift/Sources/ShopifyCheckoutProtocol/Generated/Models.swift index e9b0269c..725e85a3 100644 --- a/protocol/languages/swift/Sources/ShopifyCheckoutProtocol/Generated/Models.swift +++ b/protocol/languages/swift/Sources/ShopifyCheckoutProtocol/Generated/Models.swift @@ -12,6 +12,7 @@ import Foundation /// Base checkout schema. Extensions compose onto this using allOf. // MARK: - Checkout public struct Checkout: Codable, Sendable { + public let attribution: [String: String]? /// Representation of the buyer. public let buyer: Buyer? public let context: Context? @@ -47,7 +48,7 @@ public struct Checkout: Codable, Sendable { public let ucp: UCPCheckoutResponseSchema public enum CodingKeys: String, CodingKey { - case buyer, context + case attribution, buyer, context case continueURL = "continue_url" case currency, discounts case expiresAt = "expires_at" @@ -56,7 +57,8 @@ public struct Checkout: Codable, Sendable { case links, messages, order, payment, signals, status, totals, ucp } - public init(buyer: Buyer?, context: Context?, continueURL: String?, currency: String, discounts: CheckoutDiscounts?, expiresAt: Date?, fulfillment: CheckoutFulfillment?, id: String, lineItems: [LineItem], links: [Link], messages: [Message]?, order: OrderConfirmation?, payment: Payment?, signals: Signals?, status: CheckoutStatus, totals: [CheckoutTotal], ucp: UCPCheckoutResponseSchema) { + public init(attribution: [String: String]?, buyer: Buyer?, context: Context?, continueURL: String?, currency: String, discounts: CheckoutDiscounts?, expiresAt: Date?, fulfillment: CheckoutFulfillment?, id: String, lineItems: [LineItem], links: [Link], messages: [Message]?, order: OrderConfirmation?, payment: Payment?, signals: Signals?, status: CheckoutStatus, totals: [CheckoutTotal], ucp: UCPCheckoutResponseSchema) { + self.attribution = attribution self.buyer = buyer self.context = context self.continueURL = continueURL @@ -96,6 +98,7 @@ public extension Checkout { } func with( + attribution: [String: String]?? = nil, buyer: Buyer?? = nil, context: Context?? = nil, continueURL: String?? = nil, @@ -115,6 +118,7 @@ public extension Checkout { ucp: UCPCheckoutResponseSchema? = nil ) -> Checkout { return Checkout( + attribution: attribution ?? self.attribution, buyer: buyer ?? self.buyer, context: context ?? self.context, continueURL: continueURL ?? self.continueURL, @@ -1387,10 +1391,6 @@ public extension Link { /// Container for error, warning, or info messages. // MARK: - Message public struct Message: Codable, Sendable { - /// Warning code. Machine-readable identifier for the warning type (e.g., final_sale, prop65, - /// fulfillment_changed, age_restricted, etc.). - /// - /// Info code for programmatic handling. public let code: String? /// Human-readable message. /// @@ -2447,6 +2447,9 @@ public struct Order: Codable, Sendable { /// Post-order events (refunds, returns, credits, disputes, cancellations, etc.) that exist /// independently of fulfillment. public let adjustments: [Adjustment]? + /// Snapshot of the attribution associated with the originating checkout. Read-only on the + /// order. + public let attribution: [String: String]? /// Associated checkout ID for reconciliation. public let checkoutID: String /// ISO 4217 currency code. MUST match the currency from the originating checkout session. @@ -2469,7 +2472,7 @@ public struct Order: Codable, Sendable { public let ucp: UCPOrderResponseSchema public enum CodingKeys: String, CodingKey { - case adjustments + case adjustments, attribution case checkoutID = "checkout_id" case currency, fulfillment, id, label case lineItems = "line_items" @@ -2478,8 +2481,9 @@ public struct Order: Codable, Sendable { case totals, ucp } - public init(adjustments: [Adjustment]?, checkoutID: String, currency: String, fulfillment: Fulfillment, id: String, label: String?, lineItems: [OrderLineItem], messages: [Message]?, permalinkURL: String, totals: [CheckoutTotal], ucp: UCPOrderResponseSchema) { + public init(adjustments: [Adjustment]?, attribution: [String: String]?, checkoutID: String, currency: String, fulfillment: Fulfillment, id: String, label: String?, lineItems: [OrderLineItem], messages: [Message]?, permalinkURL: String, totals: [CheckoutTotal], ucp: UCPOrderResponseSchema) { self.adjustments = adjustments + self.attribution = attribution self.checkoutID = checkoutID self.currency = currency self.fulfillment = fulfillment @@ -2513,6 +2517,7 @@ public extension Order { func with( adjustments: [Adjustment]?? = nil, + attribution: [String: String]?? = nil, checkoutID: String? = nil, currency: String? = nil, fulfillment: Fulfillment? = nil, @@ -2526,6 +2531,7 @@ public extension Order { ) -> Order { return Order( adjustments: adjustments ?? self.adjustments, + attribution: attribution ?? self.attribution, checkoutID: checkoutID ?? self.checkoutID, currency: currency ?? self.currency, fulfillment: fulfillment ?? self.fulfillment, @@ -4143,247 +4149,5 @@ func newJSONEncoder() -> JSONEncoder { } // MARK: - Encode/decode helpers - -public class JSONNull: Codable, Hashable { - - public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool { - return true - } - - public var hashValue: Int { - return 0 - } - - public func hash(into hasher: inout Hasher) { - // No-op - } - - public init() {} - - public required init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - if !container.decodeNil() { - throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull")) - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encodeNil() - } -} - -class JSONCodingKey: CodingKey { - let key: String - - required init?(intValue: Int) { - return nil - } - - required init?(stringValue: String) { - key = stringValue - } - - var intValue: Int? { - return nil - } - - var stringValue: String { - return key - } -} - -public class JSONAny: Codable { - - public let value: Any - - static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError { - let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny") - return DecodingError.typeMismatch(JSONAny.self, context) - } - - static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError { - let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny") - return EncodingError.invalidValue(value, context) - } - - static func decode(from container: SingleValueDecodingContainer) throws -> Any { - if let value = try? container.decode(Bool.self) { - return value - } - if let value = try? container.decode(Int64.self) { - return value - } - if let value = try? container.decode(Double.self) { - return value - } - if let value = try? container.decode(String.self) { - return value - } - if container.decodeNil() { - return JSONNull() - } - throw decodingError(forCodingPath: container.codingPath) - } - - static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any { - if let value = try? container.decode(Bool.self) { - return value - } - if let value = try? container.decode(Int64.self) { - return value - } - if let value = try? container.decode(Double.self) { - return value - } - if let value = try? container.decode(String.self) { - return value - } - if let value = try? container.decodeNil() { - if value { - return JSONNull() - } - } - if var container = try? container.nestedUnkeyedContainer() { - return try decodeArray(from: &container) - } - if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) { - return try decodeDictionary(from: &container) - } - throw decodingError(forCodingPath: container.codingPath) - } - - static func decode(from container: inout KeyedDecodingContainer, forKey key: JSONCodingKey) throws -> Any { - if let value = try? container.decode(Bool.self, forKey: key) { - return value - } - if let value = try? container.decode(Int64.self, forKey: key) { - return value - } - if let value = try? container.decode(Double.self, forKey: key) { - return value - } - if let value = try? container.decode(String.self, forKey: key) { - return value - } - if let value = try? container.decodeNil(forKey: key) { - if value { - return JSONNull() - } - } - if var container = try? container.nestedUnkeyedContainer(forKey: key) { - return try decodeArray(from: &container) - } - if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) { - return try decodeDictionary(from: &container) - } - throw decodingError(forCodingPath: container.codingPath) - } - - static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] { - var arr: [Any] = [] - while !container.isAtEnd { - let value = try decode(from: &container) - arr.append(value) - } - return arr - } - - static func decodeDictionary(from container: inout KeyedDecodingContainer) throws -> [String: Any] { - var dict = [String: Any]() - for key in container.allKeys { - let value = try decode(from: &container, forKey: key) - dict[key.stringValue] = value - } - return dict - } - - static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws { - for value in array { - if let value = value as? Bool { - try container.encode(value) - } else if let value = value as? Int64 { - try container.encode(value) - } else if let value = value as? Double { - try container.encode(value) - } else if let value = value as? String { - try container.encode(value) - } else if value is JSONNull { - try container.encodeNil() - } else if let value = value as? [Any] { - var container = container.nestedUnkeyedContainer() - try encode(to: &container, array: value) - } else if let value = value as? [String: Any] { - var container = container.nestedContainer(keyedBy: JSONCodingKey.self) - try encode(to: &container, dictionary: value) - } else { - throw encodingError(forValue: value, codingPath: container.codingPath) - } - } - } - - static func encode(to container: inout KeyedEncodingContainer, dictionary: [String: Any]) throws { - for (key, value) in dictionary { - let key = JSONCodingKey(stringValue: key)! - if let value = value as? Bool { - try container.encode(value, forKey: key) - } else if let value = value as? Int64 { - try container.encode(value, forKey: key) - } else if let value = value as? Double { - try container.encode(value, forKey: key) - } else if let value = value as? String { - try container.encode(value, forKey: key) - } else if value is JSONNull { - try container.encodeNil(forKey: key) - } else if let value = value as? [Any] { - var container = container.nestedUnkeyedContainer(forKey: key) - try encode(to: &container, array: value) - } else if let value = value as? [String: Any] { - var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) - try encode(to: &container, dictionary: value) - } else { - throw encodingError(forValue: value, codingPath: container.codingPath) - } - } - } - - static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws { - if let value = value as? Bool { - try container.encode(value) - } else if let value = value as? Int64 { - try container.encode(value) - } else if let value = value as? Double { - try container.encode(value) - } else if let value = value as? String { - try container.encode(value) - } else if value is JSONNull { - try container.encodeNil() - } else { - throw encodingError(forValue: value, codingPath: container.codingPath) - } - } - - public required init(from decoder: Decoder) throws { - if var arrayContainer = try? decoder.unkeyedContainer() { - self.value = try JSONAny.decodeArray(from: &arrayContainer) - } else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) { - self.value = try JSONAny.decodeDictionary(from: &container) - } else { - let container = try decoder.singleValueContainer() - self.value = try JSONAny.decode(from: container) - } - } - - public func encode(to encoder: Encoder) throws { - if let arr = self.value as? [Any] { - var container = encoder.unkeyedContainer() - try JSONAny.encode(to: &container, array: arr) - } else if let dict = self.value as? [String: Any] { - var container = encoder.container(keyedBy: JSONCodingKey.self) - try JSONAny.encode(to: &container, dictionary: dict) - } else { - var container = encoder.singleValueContainer() - try JSONAny.encode(to: &container, value: self.value) - } - } -} +// quicktype's JSONAny/JSONNull helper suffix is intentionally replaced here. +// See ../JSONAny.swift for the maintained Swift implementation. diff --git a/protocol/languages/swift/Sources/ShopifyCheckoutProtocol/JSONAny.swift b/protocol/languages/swift/Sources/ShopifyCheckoutProtocol/JSONAny.swift new file mode 100644 index 00000000..e7eb4e8a --- /dev/null +++ b/protocol/languages/swift/Sources/ShopifyCheckoutProtocol/JSONAny.swift @@ -0,0 +1,302 @@ +// quicktype emits JSONAny/JSONNull helpers directly into Generated/Models.swift. +// protocol/scripts/generate_models.mjs verifies that helper suffix by SHA, replaces +// it with a marker comment, and relies on this file for the maintained implementation. +// Keeping these types here lets Swift tooling lint, format, and type-check them. + +// MARK: - Encode/decode helpers + +public final class JSONNull: Codable, Hashable, Sendable { + public static func == (_: JSONNull, _: JSONNull) -> Bool { + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(0) + } + + public init() {} + + public required init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if !container.decodeNil() { + throw DecodingError.typeMismatch( + JSONNull.self, + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Wrong type for JSONNull" + ) + ) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encodeNil() + } +} + +final class JSONCodingKey: CodingKey, Sendable { + let key: String + + required init?(intValue _: Int) { + return nil + } + + required init?(stringValue: String) { + key = stringValue + } + + var intValue: Int? { + return nil + } + + var stringValue: String { + return key + } +} + +private enum JSONValue: Sendable { + case bool(Bool) + case int(Int64) + case double(Double) + case string(String) + case null + case array([JSONValue]) + case object([String: JSONValue]) + + var value: Any { + switch self { + case let .bool(value): + return value + case let .int(value): + return value + case let .double(value): + return value + case let .string(value): + return value + case .null: + return JSONNull() + case let .array(values): + return values.map(\.value) + case let .object(values): + return values.mapValues { $0.value } + } + } +} + +public final class JSONAny: Codable, Sendable { + private let storage: JSONValue + + public var value: Any { + storage.value + } + + private static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError { + let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny") + return DecodingError.typeMismatch(JSONAny.self, context) + } + + private static func decode(from container: SingleValueDecodingContainer) throws -> JSONValue { + if let value = try? container.decode(Bool.self) { + return .bool(value) + } + if let value = try? container.decode(Int64.self) { + return .int(value) + } + if let value = try? container.decode(Double.self) { + return .double(value) + } + if let value = try? container.decode(String.self) { + return .string(value) + } + if container.decodeNil() { + return .null + } + throw decodingError(forCodingPath: container.codingPath) + } + + private static func decode(from container: inout UnkeyedDecodingContainer) throws -> JSONValue { + if let value = try? container.decode(Bool.self) { + return .bool(value) + } + if let value = try? container.decode(Int64.self) { + return .int(value) + } + if let value = try? container.decode(Double.self) { + return .double(value) + } + if let value = try? container.decode(String.self) { + return .string(value) + } + if let value = try? container.decodeNil() { + if value { + return .null + } + } + if var container = try? container.nestedUnkeyedContainer() { + return try decodeArray(from: &container) + } + if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) { + return try decodeDictionary(from: &container) + } + throw decodingError(forCodingPath: container.codingPath) + } + + private static func decode( + from container: inout KeyedDecodingContainer, + forKey key: JSONCodingKey + ) throws -> JSONValue { + if let value = try? container.decode(Bool.self, forKey: key) { + return .bool(value) + } + if let value = try? container.decode(Int64.self, forKey: key) { + return .int(value) + } + if let value = try? container.decode(Double.self, forKey: key) { + return .double(value) + } + if let value = try? container.decode(String.self, forKey: key) { + return .string(value) + } + if let value = try? container.decodeNil(forKey: key) { + if value { + return .null + } + } + if var container = try? container.nestedUnkeyedContainer(forKey: key) { + return try decodeArray(from: &container) + } + if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) { + return try decodeDictionary(from: &container) + } + throw decodingError(forCodingPath: container.codingPath) + } + + private static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> JSONValue { + var values: [JSONValue] = [] + while !container.isAtEnd { + let value = try decode(from: &container) + values.append(value) + } + return .array(values) + } + + private static func decodeDictionary(from container: inout KeyedDecodingContainer) throws -> JSONValue { + var values = [String: JSONValue]() + for key in container.allKeys { + let value = try decode(from: &container, forKey: key) + values[key.stringValue] = value + } + return .object(values) + } + + private static func encode(to container: inout UnkeyedEncodingContainer, array: [JSONValue]) throws { + for value in array { + try encode(to: &container, value: value) + } + } + + private static func encode( + to container: inout KeyedEncodingContainer, + dictionary: [String: JSONValue] + ) throws { + for (key, value) in dictionary { + let key = JSONCodingKey(stringValue: key)! + try encode(to: &container, value: value, forKey: key) + } + } + + private static func encode(to container: inout SingleValueEncodingContainer, value: JSONValue) throws { + switch value { + case let .bool(value): + try container.encode(value) + case let .int(value): + try container.encode(value) + case let .double(value): + try container.encode(value) + case let .string(value): + try container.encode(value) + case .null: + try container.encodeNil() + case .array, .object: + throw EncodingError.invalidValue( + value.value, + EncodingError.Context( + codingPath: container.codingPath, + debugDescription: "Cannot encode nested JSON value in a single-value container" + ) + ) + } + } + + private static func encode(to container: inout UnkeyedEncodingContainer, value: JSONValue) throws { + switch value { + case let .bool(value): + try container.encode(value) + case let .int(value): + try container.encode(value) + case let .double(value): + try container.encode(value) + case let .string(value): + try container.encode(value) + case .null: + try container.encodeNil() + case let .array(values): + var container = container.nestedUnkeyedContainer() + try encode(to: &container, array: values) + case let .object(values): + var container = container.nestedContainer(keyedBy: JSONCodingKey.self) + try encode(to: &container, dictionary: values) + } + } + + private static func encode( + to container: inout KeyedEncodingContainer, + value: JSONValue, + forKey key: JSONCodingKey + ) throws { + switch value { + case let .bool(value): + try container.encode(value, forKey: key) + case let .int(value): + try container.encode(value, forKey: key) + case let .double(value): + try container.encode(value, forKey: key) + case let .string(value): + try container.encode(value, forKey: key) + case .null: + try container.encodeNil(forKey: key) + case let .array(values): + var container = container.nestedUnkeyedContainer(forKey: key) + try encode(to: &container, array: values) + case let .object(values): + var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) + try encode(to: &container, dictionary: values) + } + } + + public required init(from decoder: Decoder) throws { + if var arrayContainer = try? decoder.unkeyedContainer() { + storage = try JSONAny.decodeArray(from: &arrayContainer) + } else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) { + storage = try JSONAny.decodeDictionary(from: &container) + } else { + let container = try decoder.singleValueContainer() + storage = try JSONAny.decode(from: container) + } + } + + public func encode(to encoder: Encoder) throws { + switch storage { + case let .array(values): + var container = encoder.unkeyedContainer() + try JSONAny.encode(to: &container, array: values) + case let .object(values): + var container = encoder.container(keyedBy: JSONCodingKey.self) + try JSONAny.encode(to: &container, dictionary: values) + default: + var container = encoder.singleValueContainer() + try JSONAny.encode(to: &container, value: storage) + } + } +} diff --git a/protocol/scripts/generate_models.mjs b/protocol/scripts/generate_models.mjs index cf90f861..6ea0583f 100755 --- a/protocol/scripts/generate_models.mjs +++ b/protocol/scripts/generate_models.mjs @@ -22,6 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import crypto from "node:crypto"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -37,6 +38,16 @@ import { const SCHEMA_SOURCE_DIR = path.join(PROTOCOL_DIR, "schemas"); const SERVICES_DIR = path.join(PROTOCOL_DIR, "services", "shopping"); +const SWIFT_JSON_HELPER_MARKER = "// MARK: - Encode/decode helpers"; +const SWIFT_JSON_HELPER_REPLACEMENT = `// MARK: - Encode/decode helpers +// quicktype's JSONAny/JSONNull helper suffix is intentionally replaced here. +// See ../JSONAny.swift for the maintained Swift implementation. +`; +// quicktype 23.2.6's Swift helper suffix for: +// --lang swift --swift-5-support --access-level public --sendable +// Guarding the whole suffix keeps this normalization fail-fast if quicktype fixes +// or changes the helper block instead of silently clobbering future output. +const QUICKTYPE_23_2_6_SWIFT_JSON_HELPER_SHA256 = "02b7721a424fdb5a586a773116130f0b273551f9bfd5d9111a1c700581ec5e7e"; function usage() { console.error("Usage: generate_models.sh --lang [--output ]"); @@ -176,9 +187,10 @@ async function prepareCodegenSchemas(tempDir) { // Message discriminators are defined across the message variant schemas. Give // each variant the same local title so quicktype emits a single MessageType symbol. for (const messageSchema of ["message_error", "message_warning", "message_info"]) { - const schema = await readJson(path.join(specDir, "types", `${messageSchema}.json`)); + const schemaPath = path.join(schemaDir, "common", "types", `${messageSchema}.json`); + const schema = await readJson(schemaPath); schema.properties.type.title = "MessageType"; - await writeJson(path.join(specDir, "types", `${messageSchema}.json`), schema); + await writeJson(schemaPath, schema); } // Extension schemas bring in repeated generic property names like `type` and @@ -268,7 +280,7 @@ function commonSchemaSources(specDir) { "--src", path.join(specDir, "order.json"), "--src", - path.join(specDir, "types", "error_response.json"), + path.join(specDir, "..", "common", "types", "error_response.json"), "--src", path.join(specDir, "instruments_change_result.json"), "--src", @@ -332,7 +344,25 @@ async function generateSwift(specDir, output) { output, ]); - await normalizeGeneratedFile(output); + await normalizeGeneratedFile(output, (source) => { + // quicktype's --sendable option marks generated models as Sendable, but quicktype 23.2.6 + // still emits dynamic JSON helper types that are not fully Swift 6 concurrency-safe. + // Drop only the exact helper suffix quicktype 23.2.6 emits. Maintained helper + // implementations live in ShopifyCheckoutProtocol/JSONAny.swift so Swift tooling can + // lint, format, and type-check them normally. + const helperStart = source.indexOf(SWIFT_JSON_HELPER_MARKER); + if (helperStart === -1) { + throw new Error("Swift JSON helper normalization failed; quicktype output may have changed"); + } + + const generatedHelper = source.slice(helperStart); + const generatedHelperHash = crypto.createHash("sha256").update(generatedHelper).digest("hex"); + if (generatedHelperHash !== QUICKTYPE_23_2_6_SWIFT_JSON_HELPER_SHA256) { + throw new Error(`Swift JSON helper normalization failed; quicktype helper output changed (sha256: ${generatedHelperHash})`); + } + + return `${source.slice(0, helperStart)}${SWIFT_JSON_HELPER_REPLACEMENT}`; + }); } async function generateTypescript(specDir, output) {