Parse-Swift icon indicating copy to clipboard operation
Parse-Swift copied to clipboard

Error while query users with emails that contains +(plus) character

Open Drendroyt opened this issue 1 year ago • 2 comments

New Issue Checklist

Issue Description

When I try to query object from my database (based on back4app) with email that contains +(plus) characters I got Parse Error "Object not found". If I try to query objects with email that contains other special characters- "_" or "." everything working ok.

Steps to reproduce

  • create _User object with email that contains +(plus) character. For example "[email protected]"
  • fetch parse object from swift code (example with login below )
    func login(user: ParseUserObject) async throws {
        guard let email = user.email?.lowercased(), let password = user.password else {
            throw ParseError.Code.unknownError
        }
        // Fetch username from Parse(user provide only email/pass for login)
        var constraints = [QueryConstraint]()
        constraints.append(equalTo(key: "email", value: email))
        let query = ParseUserObject.query(constraints)
        let parseUser = try await query.first()
        // or you can use
        // let query = ParseUserObject.query("email" == email.lowercased())
        guard let username = parseUser.username else {
            throw ParseError.Code.unknownError
        }
        try await ParseUserObject.login(username: username, password: password)
        setIsAuthorized(true)
    }

Actual Outcome

try await query.first() throw an Parse Error (object not found)

Expected Outcome

Existing object

Environment

Xcode, iOS simulator (or physical iPhone), parse database based on back4app service

Client

  • Parse Swift SDK version: 4.14.2
  • Xcode version: 15.4
  • Operating system (iOS, macOS, watchOS, etc.): iOS
  • Operating system version: 18.0

Server

  • Parse Server version: 3.10.0
  • Operating system: Linux(maybe)
  • Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): back4app server

Database

  • System (MongoDB or Postgres): MongoDB
  • Database version: 3.6
  • Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): back4app server

Logs

▿ ParseError code=101 error=Object not found on the server.

  • code : ParseSwift.ParseError.Code.objectNotFound
  • message : "Object not found on the server."
  • otherCode : nil
  • error : nil

Drendroyt avatar Dec 11 '24 14:12 Drendroyt

Thanks for opening this issue!

  • 🚀 You can help us to fix this issue faster by opening a pull request with a failing test. See our Contribution Guide for how to make a pull request, or read our New Contributor's Guide if this is your first time contributing.

as workaround for someone ... You can you back4app REST API and decode object manually.

   func getUserWithEmail(_ email: String) async throws -> ParseUserObject? {
        // Configure query json value
        let whereQuery: [String: Any] = ["email": email]
        let queryJSONData = try JSONSerialization.data(withJSONObject: whereQuery, options: [])
        guard let jsonString = String(data: queryJSONData, encoding: .utf8) else { throw URLError(.badURL) }
        
        // Configure url and encode "+" character manualy
        var urlComponents = URLComponents()
        urlComponents.scheme = "https"
        urlComponents.host = "parseapi.back4app.com"
        urlComponents.path = "/users"
        urlComponents.queryItems = [.init(name: "where", value: jsonString)]
        urlComponents.percentEncodedQuery = urlComponents.percentEncodedQuery?
            .replacingOccurrences(of: "+", with: "%2B")

        guard let url = urlComponents.url else { throw URLError(.badURL) }
        
        // Performing request
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        
        guard let appID = Bundle.main.object(forInfoDictionaryKey: "ParseAppID") as? String,
              let restApiKey = Bundle.main.object(forInfoDictionaryKey: "ParseRestApiKey") as? String
        else {
            fatalError("Cannot find Parse SDK credentials")
        }
        
        request.addValue(appID, forHTTPHeaderField: "X-Parse-Application-Id")
        request.addValue(restApiKey, forHTTPHeaderField: "X-Parse-REST-API-Key")
        
        let (data, response) = try await URLSession.shared.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode)
        else {
            throw URLError(.badServerResponse)
        }
        let resposeArray = try JSONDecoder().decode(ParseResponse<ParseUserObject>.self, from: data)
        guard let parseUser = resposeArray.results.first else { throw ParseError.Code.userWithEmailNotFound}
        return parseUser
    }

struct ParseResponse<T>: Codable where T: Codable {
    let results: [T]
}

struct ParseUserObject: ParseUser {
    enum CodingKeys: String, CodingKey {
        case username, email, emailVerified, password, authData, originalData
        case objectId, createdAt, updatedAt, ACL, displayName
    }
        
    var username: String?
    var email: String?
    var emailVerified: Bool?
    var password: String?
    var authData: [String : [String : String]?]?
    var originalData: Data?
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?
    var displayName: String?
    
    init() {}
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.username = try container.decodeIfPresent(String.self, forKey: .username)
        self.email = try container.decodeIfPresent(String.self, forKey: .email)
        self.emailVerified = try container.decodeIfPresent(Bool.self, forKey: .emailVerified)
        self.password = try container.decodeIfPresent(String.self, forKey: .password)
        self.authData = try container.decodeIfPresent([String: [String: String]?].self, forKey: .authData)
        self.originalData = try container.decodeIfPresent(Data.self, forKey: .originalData)
        self.objectId = try container.decodeIfPresent(String.self, forKey: .objectId)
        self.ACL = try container.decodeIfPresent(ParseACL.self, forKey: .ACL)
        self.displayName = try container.decodeIfPresent(String.self, forKey: .displayName)
        self.createdAt = try? container.decodeIfPresent(Date.self, forKey: .createdAt)
        self.updatedAt = try? container.decodeIfPresent(Date.self, forKey: .updatedAt)
    }
}

extension ParseUserObject {
    init(email: String, password: String) {
        self.email = email
        self.password = password
    }
}

Drendroyt avatar Dec 11 '24 18:12 Drendroyt