billp / TermiNetwork

🌏 A zero-dependency networking solution for building modern and secure iOS, watchOS, macOS and tvOS applications.
https://billp.github.io/TermiNetwork
MIT License
99 stars 8 forks source link

Unable to upload files. Wrong body type and exception is thrown #55

Closed petyots closed 1 year ago

petyots commented 1 year ago

Hi, I am trying to upload a simple png file without success. The request is throwing thread exception because of wrong body type I think.

The endpoint configuration in my repo:

case .register(let verificationId, let verificationCode, let mobilePhone, let deviceName, let userModel, let avatar):
                return .init(method: .post,
                             path: .path(["auth", "register"]),
                             params: ["mobile_phone": mobilePhone,
                                      "verification_id": verificationId,
                                      "verification_code": verificationCode,
                                      "device_id": deviceName,
                                      "first_name": userModel.firstName,
                                      "last_name": userModel.lastName,
                                      "username": userModel.username,
                                      "avatar": avatar != nil ? MultipartFormDataPartType.url(avatar!) : nil
                                     ]
                             )

This is the caller

func register(mobilePhoneE164: String, verificationId: String, verificationCode: String, user: User, avatar: URL?) async throws {
        let deviceUuid = await UIDevice.current.identifierForVendor!.uuidString

        let result = try await Client<AuthRepository>()
            .request(for: .register(verificationId: verificationId, verificationCode: verificationCode, mobilePhone: mobilePhoneE164, deviceName: deviceUuid, user, avatar: avatar))
            .asyncUpload(as: ApiResponse<AccessToken>.self, progressUpdate: nil)

        saveToken(accessToken: result.data.accessToken)
    }

And finally the exception thrown

2023-08-14 18:34:00.764788+0300 MYPROJECT[43099:16605822] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (__SwiftValue)'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000180437330 __exceptionPreprocess + 172
    1   libobjc.A.dylib                     0x0000000180051274 objc_exception_throw + 56
    2   Foundation                          0x0000000180c0ed40 _writeJSONValue + 704
    3   Foundation                          0x0000000180c12bfc ___writeJSONObject_block_invoke + 384
    4   libswiftCore.dylib                  0x000000018bf3e434 $ss26_SwiftDeferredNSDictionaryC23enumerateKeysAndObjects7options5usingySi_ys9UnmanagedVyyXlG_AHSpys5UInt8VGtXBtFTf4dnn_n + 332
    5   libswiftCore.dylib                  0x000000018bd43584 $ss26_SwiftDeferredNSDictionaryC23enumerateKeysAndObjects7options5usingySi_ys9UnmanagedVyyXlG_AHSpys5UInt8VGtXBtFTo + 44
    6   Foundation                          0x0000000180c121a0 _writeJSONObject + 440
    7   Foundation                          0x0000000180c0ea40 -[_NSJSONWriter dataWithRootObject:options:] + 84
    8   Foundation                          0x0000000180c1170c +[NSJSONSerialization dataWithJSONObject:options:error:] + 108
    9   MYPROJECT                            0x0000000104e4303c $sSD12TermiNetworkE10toJSONData10Foundation4DataVSgyKF + 168
    10  MYPROJECT                            0x0000000104e68938 $s12TermiNetwork20RequestBodyGeneratorC20generateJSONBodyData4with10Foundation0H0VSDySSypSgG_tKFZ + 96
    11  MYPROJECT                            0x0000000104e87fe8 $s12TermiNetwork7RequestC21addBodyParamsIfNeeded33_C06940D9FEEDCA294CA875886000989FLL04withC06paramsy10Foundation10URLRequestVz_SDySSypSgGSgtKF + 2016
    12  MYPROJECT                            0x0000000104e87058 $s12TermiNetwork7RequestC02asC010Foundation10URLRequestVyKF + 3176
    13  MYPROJECT                            0x0000000104e8fc74 $s12TermiNetwork18SessionTaskFactoryC08makeDataD04with17completionHandler9onFailureSo012NSURLSessiongD0CSgAA7RequestC_y10Foundation0G0VSg_So13NSURLResponseCSgtcSgyAP_AA7TNErrorOtcSgtFZ + 344
    14  MYPROJECT                            0x0000000104e88ab8 $s12TermiNetwork7RequestC011executeDataC8IfNeededyyF + 416
    15  MYPROJECT                            0x0000000104e43974 $s12TermiNetwork7RequestC7failure15responseHandlerACyAA7TNErrorOc_tF + 156
    16  MYPROJECT                            0x0000000104e45498 $s12TermiNetwork7RequestC11asyncUpload2as14progressUpdatexxm_ySi_SiSftcSgtYaKSeRzlFxyYaKXEfU_yScCyxs5Error_pGXEfU_ + 524
    17  libswift_Concurrency.dylib          0x00000001b1632a3c $ss23withCheckedContinuation8function_xSS_yScCyxs5NeverOGXEtYalFySccyxADGXEfU_Tm + 108
    18  libswift_Concurrency.dylib          0x00000001b1632960 $ss31withCheckedThrowingContinuation8function_xSS_yScCyxs5Error_pGXEtYaKlF + 196
    19  MYPROJECT                            0x0000000104e44e9d $s12TermiNetwork7RequestC11asyncUpload2as14progressUpdatexxm_ySi_SiSftcSgtYaKSeRzlFxyYaKXEfU_TQ1_ + 1
    20  MYPROJECT                            0x0000000104e451e9 $s12TermiNetwork7RequestC11asyncUpload2as14progressUpdatexxm_ySi_SiSftcSgtYaKSeRzlFxyYaKXEfU_TATQ0_ + 1
    21  libswift_Concurrency.dylib          0x00000001b16407a5 $ss27withTaskCancellationHandler9operation8onCancelxxyYaKXE_yyYbXEtYaKlFTQ0_ + 1
    22  MYPROJECT                            0x0000000104e458a9 $ss27withTaskCancellationHandler9operation8onCancelxxyYaKXE_yyYbXEtYaKlFTwbTQ1_ + 1
    23  MYPROJECT                            0x0000000104e448f1 $s12TermiNetwork7RequestC11asyncUpload2as14progressUpdatexxm_ySi_SiSftcSgtYaKSeRzlFTQ1_ + 1
    24  MYPROJECT                            0x0000000104d5e9c9 $s8MYPROJECT11AuthServiceC8register15mobilePhoneE16414verificationId0H4Code4user6avatarySS_S2SAA4UserV10Foundation3URLVSgtYaKFTQ5_ + 1
    25  MYPROJECT                            0x0000000104d93ac9 $s8MYPROJECT15SignUpViewModelC22validateFormWithServerSbyYaFTQ1_ + 1
    26  MYPROJECT                            0x0000000104d92f9d $s8MYPROJECT15SignUpViewModelC4saveyyYaFTQ1_ + 1
    27  MYPROJECT                            0x0000000104d6e351 $s8MYPROJECT10SignUpViewV4bodyQrvg7SwiftUI05TupleD0VyAE0D0PAEE15ignoresSafeArea_5edgesQrAE0jK7RegionsV_AE4EdgeO3SetVtFQOyAE5ColorV_Qo__AiEE7paddingyQrAQ_12CoreGraphics7CGFloatVSgtFQOyAiEE21navigationDestination11isPresented11destinationQrAE7BindingVySbG_qd__yXEtAeHRd__lFQOyAiEE5sheetA_9onDismiss7contentQrA3__yycSgqd__yctAeHRd__lFQOyAE6VStackVyAGyAiEE5frame5width6height9alignmentQrAY_AyE9AlignmentVtFQOyAiEE010foregroundP0yQrASSgFQOyAE5ImageV_Qo__Qo__AiEEAUyQrAQ_AYtFQOyAE4TextV_Qo_A9_yAGyAE6ButtonVyAiEEAUyQrAQ_AYtFQOyAiEEA10_A11_A12_A13_QrAY_AYA15_tFQOyAiEE7overlay_A13_Qrqd___A15_tAeHRd__lFQOyAE5ShapePAEE4fill_5styleQrqd___AE9FillStyleVtAE10ShapeStyleRd__lFQOyAE6CircleV_ASQo__AE5GroupVyAE19_ConditionalContentVyAE6ZStackVyAGyAiEE9clipShape_A31_Qrqd___A33_tAEA28_Rd__lFQOyAA021CompleteProfileAvatarD0V_A36_Qo__AiEE6offset1x1yQrAX_AXtFQOyA26_yAiEEA16_yQrA17_FQOyAiEE4fontyQrAE4FontVSgFQOyA19__Qo__Qo_G_Qo_tGGA19_GGQo__Qo__Qo_G_AiEE8onSubmit2of_QrAE14SubmitTriggersV_yyctFQOyAiEEAUyQrAQ_AYtFQOyAI13FormValidatorE10validationyQrA71_19ValidationContainerVSgFQOyAiEE11submitLabelyQrAE11SubmitLabelVFQOyAiEE12cornerRadius_11antialiasedQrAX_SbtFQOyAiEE10background_0ijK5EdgesQrqd___AQtAEA34_Rd__lFQOyAiEEAUyQrAQ_AYtFQOyAiEEAUyQrAQ_AYtFQOyAiEE27textInputAutocapitalizationyQrAE27TextInputAutocapitalizationVSgFQOyAiEE22autocorrectionDisabledyQrSbFQOyAiEE15textContentTypeyQrSo17UITextContentTypeaSgFQOyAiEE12keyboardTypeyQrSo14UIKeyboardTypeVFQOyAiEE7focused_6equalsQrAE10FocusStateVA1_Vyqd___G_qd__tSHRd__lFQOyAiEEA16_yQrA17_FQOyAE9TextFieldVyA23_G_Qo__AA24CompleteProfileFormFocusOSgQo__Qo__Qo__Qo__Qo__Qo__Qo__ASQo__Qo__Qo__Qo__Qo__Qo_AiEEAUyQrAQ_AYtFQOyAIA71_EA72_yQrA75_FQOyAiEEA76_yQrA78_FQOyAiEEA79__A80_QrAX_SbtFQOyAiEEA81__A82_Qrqd___AQtAEA34_Rd__lFQOyAiEEAUyQrAQ_AYtFQOyAiEEAUyQrAQ_AYtFQOyAiEEA88_yQrA91_FQOyAiEEA83_yQrA86_FQOyAiEEA87_yQrSbFQOyA109__Qo__Qo__Qo__Qo__Qo__ASQo__Qo__Qo__Qo__Qo_A130_tGGAE6SpacerVAA06ButtonD0VtGG_16ExyteMediaPicker11MediaPickerVyAE05EmptyD0VA143_GQo__AA015OtpVerificationD0VQo__Qo_tGyXEfU_A137_yXEfU_yycfU0_yyYaYbcfU_TQ1_ + 1
    28  MYPROJECT                            0x0000000104d75c9d $s8MYPROJECT10SignUpViewV4bodyQrvg7SwiftUI05TupleD0VyAE0D0PAEE15ignoresSafeArea_5edgesQrAE0jK7RegionsV_AE4EdgeO3SetVtFQOyAE5ColorV_Qo__AiEE7paddingyQrAQ_12CoreGraphics7CGFloatVSgtFQOyAiEE21navigationDestination11isPresented11destinationQrAE7BindingVySbG_qd__yXEtAeHRd__lFQOyAiEE5sheetA_9onDismiss7contentQrA3__yycSgqd__yctAeHRd__lFQOyAE6VStackVyAGyAiEE5frame5width6height9alignmentQrAY_AyE9AlignmentVtFQOyAiEE010foregroundP0yQrASSgFQOyAE5ImageV_Qo__Qo__AiEEAUyQrAQ_AYtFQOyAE4TextV_Qo_A9_yAGyAE6ButtonVyAiEEAUyQrAQ_AYtFQOyAiEEA10_A11_A12_A13_QrAY_AYA15_tFQOyAiEE7overlay_A13_Qrqd___A15_tAeHRd__lFQOyAE5ShapePAEE4fill_5styleQrqd___AE9FillStyleVtAE10ShapeStyleRd__lFQOyAE6CircleV_ASQo__AE5GroupVyAE19_ConditionalContentVyAE6ZStackVyAGyAiEE9clipShape_A31_Qrqd___A33_tAEA28_Rd__lFQOyAA021CompleteProfileAvatarD0V_A36_Qo__AiEE6offset1x1yQrAX_AXtFQOyA26_yAiEEA16_yQrA17_FQOyAiEE4fontyQrAE4FontVSgFQOyA19__Qo__Qo_G_Qo_tGGA19_GGQo__Qo__Qo_G_AiEE8onSubmit2of_QrAE14SubmitTriggersV_yyctFQOyAiEEAUyQrAQ_AYtFQOyAI13FormValidatorE10validationyQrA71_19ValidationContainerVSgFQOyAiEE11submitLabelyQrAE11SubmitLabelVFQOyAiEE12cornerRadius_11antialiasedQrAX_SbtFQOyAiEE10background_0ijK5EdgesQrqd___AQtAEA34_Rd__lFQOyAiEEAUyQrAQ_AYtFQOyAiEEAUyQrAQ_AYtFQOyAiEE27textInputAutocapitalizationyQrAE27TextInputAutocapitalizationVSgFQOyAiEE22autocorrectionDisabledyQrSbFQOyAiEE15textContentTypeyQrSo17UITextContentTypeaSgFQOyAiEE12keyboardTypeyQrSo14UIKeyboardTypeVFQOyAiEE7focused_6equalsQrAE10FocusStateVA1_Vyqd___G_qd__tSHRd__lFQOyAiEEA16_yQrA17_FQOyAE9TextFieldVyA23_G_Qo__AA24CompleteProfileFormFocusOSgQo__Qo__Qo__Qo__Qo__Qo__Qo__ASQo__Qo__Qo__Qo__Qo__Qo_AiEEAUyQrAQ_AYtFQOyAIA71_EA72_yQrA75_FQOyAiEEA76_yQrA78_FQOyAiEEA79__A80_QrAX_SbtFQOyAiEEA81__A82_Qrqd___AQtAEA34_Rd__lFQOyAiEEAUyQrAQ_AYtFQOyAiEEAUyQrAQ_AYtFQOyAiEEA88_yQrA91_FQOyAiEEA83_yQrA86_FQOyAiEEA87_yQrSbFQOyA109__Qo__Qo__Qo__Qo__Qo__ASQo__Qo__Qo__Qo__Qo_A130_tGGAE6SpacerVAA06ButtonD0VtGG_16ExyteMediaPicker11MediaPickerVyAE05EmptyD0VA143_GQo__AA015OtpVerificationD0VQo__Qo_tGyXEfU_A137_yXEfU_yycfU0_yyYaYbcfU_TATQ0_ + 1
    29  MYPROJECT                            0x0000000104d56e01 $sxIeghHr_xs5Error_pIegHrzo_s8SendableRzs5NeverORs_r0_lTRTQ0_ + 1
    30  MYPROJECT                            0x0000000104d56f41 $sxIeghHr_xs5Error_pIegHrzo_s8SendableRzs5NeverORs_r0_lTRTATQ0_ + 1
    31  libswift_Concurrency.dylib          0x00000001b1660445 _ZL23completeTaskWithClosurePN5swift12AsyncContextEPNS_10SwiftErrorE + 1
)
libc++abi: terminating due to uncaught exception of type NSException
(Recorded stack frame) 
#0  0x0000000180437324 in __exceptionPreprocess ()
#1  0x0000000180051274 in objc_exception_throw ()
#2  0x0000000180c0ed40 in _writeJSONValue ()
#3  0x0000000180c12bfc in ___writeJSONObject_block_invoke ()
#4  0x000000018bf3e434 in specialized _SwiftDeferredNSDictionary.enumerateKeysAndObjects(options:using:) ()
#5  0x000000018bd43584 in @objc _SwiftDeferredNSDictionary.enumerateKeysAndObjects(options:using:) ()
#6  0x0000000180c121a0 in _writeJSONObject ()
#7  0x0000000180c0ea40 in -[_NSJSONWriter dataWithRootObject:options:] ()
#8  0x0000000180c1170c in +[NSJSONSerialization dataWithJSONObject:options:error:] ()
#9  0x0000000104e4303c in Dictionary.toJSONData() at /myproject/-aorjesgwpwvgdfbmtmzrwsqletef/SourcePackages/checkouts/TermiNetwork/Source/Extensions/Dictionary+Extensions.swift:24
#10 0x0000000104e68938 in static RequestBodyGenerator.generateJSONBodyData(with:) at /myproject/-aorjesgwpwvgdfbmtmzrwsqletef/SourcePackages/checkouts/TermiNetwork/Source/Helpers/RequestBodyGenerators.swift:60
#11 0x0000000104e87fe8 in Request.addBodyParamsIfNeeded(withRequest:params:) at /myproject/-aorjesgwpwvgdfbmtmzrwsqletef/SourcePackages/checkouts/TermiNetwork/Source/Request.swift:291
#12 0x0000000104e87058 in Request.asRequest() at /myproject/-aorjesgwpwvgdfbmtmzrwsqletef/SourcePackages/checkouts/TermiNetwork/Source/Request.swift:240
#13 0x0000000104e8fc74 in static SessionTaskFactory.makeDataTask(with:completionHandler:onFailure:) at /myproject/-aorjesgwpwvgdfbmtmzrwsqletef/SourcePackages/checkouts/TermiNetwork/Source/SessionTaskFactory.swift:35
#14 0x0000000104e88ab8 in Request.executeDataRequestIfNeeded() at /myproject/-aorjesgwpwvgdfbmtmzrwsqletef/SourcePackages/checkouts/TermiNetwork/Source/Request.swift:352
#15 0x0000000104e43974 in Request.failure(responseHandler:) at /myproject/-aorjesgwpwvgdfbmtmzrwsqletef/SourcePackages/checkouts/TermiNetwork/Source/Extensions/Operations/Request+DataOperations.swift:37
#16 0x0000000104e45498 in closure #1 in closure #1 in Request.asyncUpload<τ_0_0>(as:progressUpdate:) at /myproject/-aorjesgwpwvgdfbmtmzrwsqletef/SourcePackages/checkouts/TermiNetwork/Source/Extensions/Operations/Request+FileOperationsAsync.swift:43
#17 0x00000001b1632a3c in closure #1 in withCheckedContinuation<τ_0_0>(function:_:) ()
#18 0x00000001b1632960 in withCheckedThrowingContinuation<τ_0_0>(function:_:) ()
#19 0x0000000104e44e9d in closure #1 in Request.asyncUpload<τ_0_0>(as:progressUpdate:) at /myproject/-aorjesgwpwvgdfbmtmzrwsqletef/SourcePackages/checkouts/TermiNetwork/Source/Extensions/Operations/Request+FileOperationsAsync.swift:37
#20 0x0000000104e451e9 in partial apply for closure #1 in Request.asyncUpload<τ_0_0>(as:progressUpdate:) ()
#21 0x00000001b16407a5 in withTaskCancellationHandler<τ_0_0>(operation:onCancel:) ()
#22 0x0000000104e458a9 in withTaskCancellationHandler<τ_0_0>(operation:onCancel:) ()
#23 0x0000000104e448f1 in Request.asyncUpload<τ_0_0>(as:progressUpdate:) at /myproject/-aorjesgwpwvgdfbmtmzrwsqletef/SourcePackages/checkouts/TermiNetwork/Source/Extensions/Operations/Request+FileOperationsAsync.swift:35
#24 0x0000000104d5e9c9 in AuthService.register(mobilePhoneE164:verificationId:verificationCode:user:avatar:) at /Users/myuser/Developer/MYPROJECT/Code/apps/MYPROJECT/MYPROJECT/Services/AuthService.swift:94
#25 0x0000000104d93ac9 in SignUpViewModel.validateFormWithServer() at /Users/myuser/Developer/MYPROJECT/Code/apps/MYPROJECT/MYPROJECT/ViewModels/Authentication/SignUpViewModel.swift:78
#26 0x0000000104d92f9d in SignUpViewModel.save() at /Users/myuser/Developer/MYPROJECT/Code/apps/MYPROJECT/MYPROJECT/ViewModels/Authentication/SignUpViewModel.swift:61
#27 0x0000000104d6e351 in closure #1 in closure #2 in closure #1 in closure #1 in SignUpView.body.getter at /Users/myuser/Developer/MYPROJECT/Code/apps/MYPROJECT/MYPROJECT/Views/Screens/Authentication/SignUpView.swift:166
#28 0x0000000104d75c9d in partial apply for closure #1 in closure #2 in closure #1 in closure #1 in SignUpView.body.getter ()
#29 0x0000000104d56e01 in thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out τ_0_0) ()
#30 0x0000000104d56f41 in partial apply for thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out τ_0_0) ()
#31 0x00000001b1660445 in completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) ()

I am thinking it's because of wrong body type since the request is trying to write json output while the avatar param is MultiPart

Once this is resolved I will try to find time to create PR with documentation on uploading

EDIT:

case .register(let verificationId, let verificationCode, let mobilePhone, let deviceName, let userModel, let avatar):
                let config = Configuration()
                config.requestBodyType = .multipartFormData(boundary: UUID().uuidString)
                return .init(method: .post,
                             path: .path(["auth", "register"]),
                             params: ["mobile_phone": MultipartFormDataPartType.value(value: mobilePhone),
                                      "verification_id": MultipartFormDataPartType.value(value: verificationId),
                                      "verification_code": MultipartFormDataPartType.value(value: verificationCode),
                                      "device_id": MultipartFormDataPartType.value(value: deviceName),
                                      "first_name": MultipartFormDataPartType.value(value: userModel.firstName),
                                      "last_name": MultipartFormDataPartType.value(value: userModel.lastName),
                                      "username": MultipartFormDataPartType.value(value: userModel.username),
                                      "avatar": avatar != nil ? MultipartFormDataPartType.url(avatar!) : nil
                                     ],
                             configuration: config
                             )

By explicitly configuring the body as multipart its working fine. But if verbose is enabled it does not work in either way so I think the issue is in asyncUpload() not setting the appropriate body type.

EDIT 1:

When no file is passed even though the body is same as with avatar the request is empty.

billp commented 1 year ago

Hello @petyots, I think the problem is that you try to insert nil conditionally as avatar parameter. The following code filters out any nil value from the params.

case .register(let verificationId, let verificationCode, let mobilePhone, let deviceName, let userModel, let avatar):
                return .init(method: .post,
                             path: .path(["auth", "register"]),
                             params: ["mobile_phone": mobilePhone,
                                      "verification_id": verificationId,
                                      "verification_code": verificationCode,
                                      "device_id": deviceName,
                                      "first_name": userModel.firstName,
                                      "last_name": userModel.lastName,
                                      "username": userModel.username,
                                      "avatar": avatar != nil ? MultipartFormDataPartType.url(avatar!) : nil
                                     ].compactMapValues { $0 }
                             )

Please let me know if it works.

petyots commented 1 year ago

@billp Thanks for your time.

Unfortunately no joy...

2023-08-14 23:15:17.978950+0300 BULLTIME[54434:16877627] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (__SwiftValue)'

case .register(let verificationId, let verificationCode, let mobilePhone, let deviceName, let userModel, let avatar):
                let config = Configuration()
                //config.requestBodyType = .multipartFormData(boundary: UUID().uuidString)
                //config.verbose = true
                return .init(method: .post,
                             path: .path(["auth", "register"]),
                             params: ["mobile_phone": mobilePhone,
                                      "verification_id": verificationId,
                                      "verification_code": verificationCode,
                                      "device_id": deviceName,
                                      "first_name":  userModel.firstName,
                                      "last_name": userModel.lastName,
                                      "username": userModel.username,
                                      "avatar": avatar != nil ? MultipartFormDataPartType.url(avatar!) : nil
                                     ].compactMapValues { $0 },
                             configuration: config
                             )

If I avatar is nil it's fine but when I pass it down it throws

billp commented 1 year ago

@petyots Sorry, I forgot to add the MultipartFormDataPartType.value to the rest of the params. The point is that when calling upload you should use either MultipartFormDataPartType.value, MultipartFormDataPartType.url or MultipartFormDataPartType.data, otherwise it doesn't now how to append it to upload body. Try this, it should work:

case .register(let verificationId, let verificationCode, let mobilePhone, let deviceName, let userModel, let avatar):
                let config = Configuration()
                //config.requestBodyType = .multipartFormData(boundary: UUID().uuidString)
                //config.verbose = true
                return .init(method: .post,
                             path: .path(["auth", "register"]),
                             params: ["mobile_phone": MultipartFormDataPartType.value(mobilePhone),
                                      "verification_id": MultipartFormDataPartType.value(verificationId),
                                      "verification_code": MultipartFormDataPartType.value(verificationCode),
                                      "device_id": MultipartFormDataPartType.value(deviceName),
                                      "first_name":  MultipartFormDataPartType.value(userModel.firstName),
                                      "last_name": MultipartFormDataPartType.value(userModel.lastName),
                                      "username": MultipartFormDataPartType.value(userModel.username),
                                      "avatar": avatar != nil ? MultipartFormDataPartType.url(avatar!) : nil
                                     ].compactMapValues { $0 },
                             configuration: config
                             )

I will think about getting rid of MultipartFormDataPartType.[type] in a future release and use the value directly.

Also I think you don't need the config param. It will inherit it from parent.

Does it work now?

petyots commented 1 year ago

@billp 1000x It's working now as it should. Thanks again!. Maybe we can add some useful docs? What do you think

billp commented 1 year ago

You're welcome @petyots. Yes sure, I will add the usage details in README in the next days. Thank you!