New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
When using beforeSave on object with a Pointer column - "error: ParseError code=111 error=schema mismatch for Test.test1; expected Pointer<Test1> but got Object" #65
Comments
See my response here: netreconlab/Parse-Swift#151 (comment) |
I think this is a different problem. it wasn't a matter of it having the same schema or anything like that. I was just passing an already existing object, which has pointed to another object (already existing) it didn't have to create anything. But passing the object through Parse-Server-Swift BeforeSave causes this error. i just tested beforeSave on JS cloudcode and it updated fine. This has something to do with Pointer and XXXXX object and how ParseSwift handles it. I'm just not sure exactly where it goes wrong. |
I don’t see a “beforeSave” trigger in the code you provided. I do see “beforeSaveTest” but that’s not a beforeSave trigger |
sorry, I call func boot(routes: RoutesBuilder) throws {
let lokalityGroup = routes.grouped("lokality")
//: Triggers
lokalityGroup.post("save", "before", object: Lokality.self,
trigger: .beforeSave, use: beforeSave)
lokalityGroup.post("save", "before", object: Test.self,
trigger: .beforeSave, use: beforeSaveTest)
} with logs
so it does get called on .save() |
It still looks like the same problem as #65 (comment) to me. ParseServerSwift uses ParseSwift, the beforeSave webhook is called before an object is created on the server so it will run into the same issue. I think you can improve on your systems design to circumvent this, like not saving empty items for no reason. You should save objects when they actually have data. I gave some examples in the previous links. If you insist on keeping your design, you may need to remove your “beforeSave” code using ParseServerSwift, and us the JS Cloud Code for that part. You could also never create empty objects on the client, use ParseServerSwift beforeSave to create them if they are nil |
It's not empty though. They're already there and existing. |
So it's not a matter of creating empty objects this time around but how ParseServerSwift handles saving a Pointer but receiving an object in beforeSave. Somehow it sees the struct and thinks it's a 'Pointer' but really it's getting a 'Test1' - in beforeSave. |
I'll give it a try on more of my other classes that have the same setup of pointers a do a beforeSave that already has existing data and see if i get it saving. The Test classes I use are for posting here on Github but I initially encountered the error on my other classes. |
I got it to save just now by changing The save was just updating an existing object. struct Test: ParseObject {
//: These are required for `ParseObject`.
var originalData: Data?
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var name: String?
var test1: Test1?
} to var test1: Pointer<Test1>? If that gives you an idea of where we can look? This "fix" isn't workable as I'd have to |
I recommend looking at the unit tests and try to create a PR which replicates your scenario. You should be able to do it in the ParseSwift repo as it supports all of the hooks. The test case will most likely go into this file: https://github.com/netreconlab/Parse-Swift/blob/main/Tests/ParseSwiftTests/ParseHookTriggerTests.swift. |
My coding skills haven't gotten that far yet. 😄 I mean I've done a few. It's how I test the methods. but not sure how to test the hooks yet directly like in that ParseHookTriggerTest.swift. I'll look into it though. thanks |
What happens when you try the following?
|
func testTest() async {
do {
let testQuery = Test.query()
var result = try await testQuery.first(options: [.usePrimaryKey])
// result.name = "wohoo1234"
result.set(\.name, to: "wohoo4321")
try await result.save(options: [.usePrimaryKey])
print("Successfully updated: \(result.objectId.logable)")
} catch {
print("error: \(error.localizedDescription)")
XCTAssertNil("Update Test failed")
}
} still the same error |
I'm still trying to figure out how to do a Unit Test without an actual database though - for the PR. |
The test file I referenced before shows how to do this: Server response: https://github.com/netreconlab/Parse-Swift/blob/b3169f2b438df9bb1d14935246be0a462c97c42f/Tests/ParseSwiftTests/ParseHookTriggerTests.swift#L188-L201 There are 1000+ examples in the current unit tests. Look for one that seems more intuitive, then go to more complex |
How do I actually trigger the beforeSave Trigger in a test? I can't seem to find an existing test that actually calls it or runs it. everything seems to be just creating / updating hooks. |
I haven't been able to successfully do a unit test with my case but I have figured out what's causing the problem. In JS CloudCode, the object I get out of the Request vs the paramsRequest.object (which was decoded) is structured differently. In JS CloudCode, the object keeps the
converts the Pointer to just an Object which is then what we pass to the ParseHookResponse at the end of the beforeSave
Question is, how do I pull out the data I need from the Request (while in the beforeSave trigger function) without decoding it or decode it in a way that I keep the Pointer structure? func beforeSaveTest(req: Request) async throws -> ParseHookResponse<Test> {
if let error: HookResponse<Test> = checkHeaders(req) { return error }
// object structure is changed here which JS validation errors out later in SchemaController.js
var parseRequest = try req.content.decode(ParseHookTriggerObjectRequest<User, Test>.self)
let options = try parseRequest.options(req)
if parseRequest.user != nil {
parseRequest = try await parseRequest.hydrateUser(options: options, request: req)
}
guard var object = parseRequest.object else {
return ParseHookResponse(error: .init(code: .missingObjectId,
message: "Object not sent in request."))
}
return HookResponse(success: object)
} SchemaController.js in parse-server |
I think I may understand where the issue is at now, at this line: parse-server-swift/Sources/ParseServerSwift/Parse.swift Lines 49 to 50 in 12db874
Change to: // Parse uses tailored encoders/decoders. These can be retrieved from any ParseObject
ContentConfiguration.global.use(encoder: User.getEncoder() /* Change the encoder here */, for: .json) Delete all of your hooks and functions before connecting the updated ParseServer and let me know if this works. It's possible this may introduce other issues that weren't originally there since parse-server-swift is only connecting to a ParseServer and not something that expects traditional JSON. If there are new issues, we will need to look further into how to better utilize Vapors encoder configuration. |
I've been trying to pull the data out "as is" of the HTTP header Request but couldn't figure it out. I'm not well versed in decoding yet. usually I just decode json. |
It doesn't look like it's a decoding issue, it looks more like an encoding issue because the parse-server expects Parse json to look a particular way. Objects that can be pointers should be sent as pointers which is why #65 (comment) works. I'll look into a fix when I get some time next week. If you you really want to get it working now, create two objects for ParseObjects that contain pointers: // This is the same as the original, but add the `convertForSending` method
struct Test: ParseObject {
//: These are required for `ParseObject`.
var originalData: Data?
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var name: String?
var test1: Test1?
func convertForSending() -> TestForSendingOnly {
var convertedObject = TestForSendingOnly()
// Copy all properties manually
convertedObject.createdAt = try? self.createdAt
// ...
convertedObject.test1 = try? self.test1.toPointer()
return convertedObject
}
}
// This almost looks like the original, but has pointers
struct TestForSendingOnly: ParseObject {
//: These are required for `ParseObject`.
var originalData: Data?
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var name: String?
var test1: Pointer<Test1>?
}
// Cloud Code
func beforeSaveTest(req: Request) async throws -> ParseHookResponse<TestForSendingOnly> {
if let error: HookResponse<Test> = checkHeaders(req) { return error }
var parseRequest = try req.content.decode(ParseHookTriggerObjectRequest<User, Test>.self)
let options = try parseRequest.options(req)
if parseRequest.user != nil {
parseRequest = try await parseRequest.hydrateUser(options: options, request: req)
}
guard var object = parseRequest.object else {
return ParseHookResponse(error: .init(code: .missingObjectId,
message: "Object not sent in request."))
}
let convertedObject = object.convertForSending()
return HookResponse(success: convertedObject)
} |
ah didn't think of that workaround. but given that I have lots of objects with lots of pointers, I think I'll wait for your fix for this then. thanks again! |
When using beforeSave trigger on an object that has a pointer to another object like so:
Doing nothing but just having the beforeSave trigger on produces a schema mismatch error where it expects a Pointer to the sub-object.
I encountered this as I was trying to save a complex object with multiple pointers and tested with these Test objects getting the same result.
The text was updated successfully, but these errors were encountered: