Skip to content
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

Decoder for Atlas Functions result #8137

Open
solkpolk opened this issue Feb 16, 2023 · 4 comments
Open

Decoder for Atlas Functions result #8137

solkpolk opened this issue Feb 16, 2023 · 4 comments

Comments

@solkpolk
Copy link

Problem

Decoding results from Atlas App Functions. I'm assuming a lot of people using atlas app functions would like this feature

Solution

No response

Alternatives

No response

How important is this improvement for you?

Would be a major improvement

Feature would mainly be used with

Atlas App Services: Auth or Functions etc

@nirinchev
Copy link
Member

Hey, we've been chatting with the team on how to approach this. In the meantime, you could implement your own functions client and use the SwiftBSON library to encode and decode the request and response. I'm not a swift developer, but I threw together some code that should help you get started:

import RealmSwift
import Realm
import SwiftBSON

struct FunctionClient {
    let functionsUrl: URL
    let user: User
    let _decoder = ExtendedJSONDecoder()
    let _encoder = ExtendedJSONEncoder()
    
    init(appId: String, app: App, user: User) {
        functionsUrl = URL(string: "\(app.configuration.baseURL!)/api/client/v2.0/app/\(appId)/functions/call")!
        self.user = user
    }
    
    func callFunction<T: Decodable>(_: T.Type, name: String, args: [Encodable]) async throws -> T {
        var request = URLRequest(url: functionsUrl)
        request.setValue( "Bearer \(user.accessToken!)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        
        let serializedArgs = try args.map {
            return try String(decoding: _encoder.encode($0), as: UTF8.self)
        }.joined(separator: ",")
        
        let body = "{ \"name\": \"\(name)\", \"arguments\": [\(serializedArgs)] }"
        
        request.httpBody = Data(body.utf8)
        
        let (data, response) = try await URLSession.shared.data(for: request)
        if let httpResponse = response as? HTTPURLResponse {
            if httpResponse.statusCode == 401 {
                try await user.refreshCustomData()
                return try await callFunction(T.self, name: name, args: args)
            }
        }

        return try _decoder.decode(T.self, from: data)
    }
}

And you use it like:

// Define your strongly-typed model representing the result
struct FunctionResult: Codable, Equatable {
    let result: Int32
}

let functionClient = FunctionClient(appId: "my-app-id", app: app, user: user)
let result = try await functionClient.callFunction(FunctionResult.self, name: "addNumbers", args: [1, 2])

It's not the most elegant solution and the code can certainly be made more swift-y, but I hope it'll unblock you until we have a proper first-party API.

@solkpolk
Copy link
Author

Nice idea!
I tried running it but discovered app.configuration.baseURL doesn't exist. Is there another way to obtain the baseURL?

@jsflax
Copy link
Contributor

jsflax commented Feb 17, 2023

@solkpolk I opened up a draft for you: #8142

import SwiftBSON

private struct Preferences: Codable {
     let favoriteColor: String
     let apples: Int
}

func testUserCallFunctionAsyncAwaitCodable() async throws {
    let user = try await self.app.login(credentials: basicCredentials())
    let preferences = try ExtendedJSONDecoder().decode(Preferences.self,
                                                       from: try await user.functions.preferences())

    XCTAssertEqual(preferences.favoriteColor, "green")
    XCTAssertEqual(preferences.apples, 10)
}

Using my branch, you should be able to use the normal pathway (user.functions.(...)) with an overloaded return of Data, which you can then hook into the ExtendedJSONDecoder from SwiftBSON. We'll look into more permanent solutions in the near future.

@solkpolk
Copy link
Author

Works. Many thanks 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants