Code Monkey home page Code Monkey logo

papyrus's People

Contributors

f0x1d avatar jayrhynas avatar jordanekay avatar joshuawright11 avatar kevin-kp avatar lutes1 avatar noahkamara avatar the-braveknight avatar tomrudnick avatar wouter01 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

papyrus's Issues

Ambiguous use of 'addParameter(_:value:)'

When creating an api with the following:

@GET("/some/endpoint/:param")
func getSomething(param: Path<Param>) async throws -> Something

Where Param is an enum with a raw value of String

enum Param: String, Codable, CaseIterable {
        case something
        case somethingElse = "something-else"
}

The following error occurs when building: Ambiguous use of 'addParameter(_:value:)'

PapyrusAlamofire not working

Macro generated API struct tries to access Papyrus.Provider, but if we use PapyrusAlamofire, then we should access PapyrusAlamofire.Provider.

So, i just get:
image

Generated macro:
image

Request streaming

It will be cool and convenient if Papyrus supports requests streaming

Allow returning `Data` as a response type

I tried writing "Data" as return type, but I got the response is not JSON as error. Is there any way to add a custom decoder or any other better way to do a binary file download request?

Callback-based API

Some folks haven't upgraded to async yet so an option for a callback based API could be a useful addition.

The @API macro would just check to see if the function signature is async throws or if the last argument is a closure.

CI

Will need Xcode 15 support via GitHub actions & code coverage is broken in the current beta anyways.

Automatically add default values for optional types

HI there!

It would be nice if Papyrus added default values when generating API implementations. Personally, I have a lot of functions that look like this:

    @POST("/sendMessage")
    func sendMessage(
        chatId: TelegramIdentifier,
        messageThreadId: Int?,
        text: String,
        parseMode: ParseMode?,
        entities: [MessageEntity]?,
        linkPreviewOptions: LinkPreviewOptions?,
        disableNotification: Bool?,
        protectContent: Bool?,
        replyParameters: ReplyParameters?,
        replyMarkup: ReplyMarkup?
    ) async throws -> TelegramResponse<Message>

Currently, I have to either:

  1. Pass nil to every single parameter, which doesn't work for the API I'm consuming (and I guess it wouldn't work for a lot of APIs either);
  2. Create several functions with the same path so I wouldn't have to use nil (which is a very horrible approach);
  3. Modify the @API macro so it adds default values for every parameter with an optional type.

I have a fork that implements the third option. However, I also had to modify the request builder so nil values aren't added to the request body, which may not be a very good idea if you have to explicitly set some value as nil.
If you have any other ideas, I'd be happy to implement them and open a PR. Maybe adding another macro, something like @AddDefaultValues?

Thanks!

Request Retry

There should be a type similar to Interceptor that allows custom logic to be run after a failed request (such as retry, refresh auth token, etc).

Some useful default implementations (retry, retryAfterDelay, etc) should be available out of the box.

@JSON encoder argument

@POST("user/getDetails")
@JSON(decoder: JSONDecoder())
func getUserInfo() async throws -> UserInfo

which expends

func getUserInfo() async throws -> UserInfo {
    var req = builder(method: "POST", path: "/user/getDetails")
    req.requestEncoder = .json(JSONDecoder())
    req.responseDecoder = .json(JSONDecoder())
    let res = try await provider.request(req)
    return try res.decode(UserInfo.self, using: req.responseDecoder)
}

Invalid request URL when baseURL is missing trailing `/`

Description

Initializing a Provider with a baseURL without a trailing / will produce a request with a malformed URL.

How to reproduce

@API
protocol Github {
    @GET("/users/:username/repos")
    func getRepositories(username: String) async throws -> Response
}

let provider = Provider(baseURL: "https://api.github.com")
let github = GithubAPI(provider: provider)
let response = try await github.getRepositories(username: "alchemy-swift")

The built request will have an URL with a missing /:

https://api.github.comusers/alchemy-swift/repos

Xcode Cloud trust issue

Xcode Cloud complains that "Target 'Papyrus' must be enabled before it can be used." and gives up. Haven't seen this issue with other Swift packages. Any fixes?

Void return value support

Sometimes we just make API calls to report data without caring about the API response.

@POST("user/openApp")
func openApp() async throws -> Void

==>

func openApp() async throws -> Void {
    let req = builder(method: "POST", path: "user/openApp")
    let res = try await provider.request(req)
    return try res.decode(Void.self, using: req.responseDecoder)  // <- Error: Type 'Void' cannot conform to 'Decodable'

}

Error when trying to set path parameter in front of static query

When trying to set a path parameter that sits in front of a static query like bar/:baz?query=1 papyrus fails to find the path parameter:

PapyrusError(
  message: "Tried to set path parameter `baz` but did not find `:baz` in path `bar/:baz?query=1`.", 
  request: nil, 
  response: nil
)

Test Case:

func testPathWithStaticQuery() {
        var req = RequestBuilder(baseURL: "foo/", method: "GET", path: "bar/:baz?query=1")
        req.addParameter("baz", value: "value")
        
        XCTAssertNoThrow(try req.fullURL())
        XCTAssertEqual(try req.fullURL().absoluteString, "foo/bar/value/?query=1")
    }

I'd be willing to work on this, since i need it for a project

Support for default values in Request

Hi, first of all, thanks for writing such a nice macros.

There was one thing that I found disappointing.
The Swift syntax does not allow you to set default values for parameters in protocol functions.
If it's a normal protocol function, you can set the default value of the function parameter through an extension, but since it's a macro, it doesn't seem possible.

I don't know much about macros, but here's what I can think of.

@API
protocol Users {
  @GET("/users")
  func getList(size: Int = 20, cursor: String?) async throws -> [User]

  @POST("/user")
  func createUser(email: String, password: String, nickname: String? = nil) async throws
}

If it is possible to have an implementation that violates Swift syntax like the one above with just a macros, then the above seems to be the most convenient and reasonable.

@API
protocol Users {
  @GET("/users")
  func getList(@Default(20) size: Int, cursor: String?) async throws -> [User]

  @POST("/user")
  func createUser(email: String, password: String, @Default(nil) nickname: String?) async throws
}

Looking at the comments in the code and the post at, it seems that this is probably not possible at the moment, as there is no macros specification. If a spec is added in the future, this would be fine.

In addition to this, if there is a way to do this in the current implementation without a new one, I'd to hear about it!

Multipart encoding not working

I have such api:

@API
@Mock
protocol Images {
    @Multipart
    @POST("/image/upload")
    func uploadImage(file: Part) async throws -> UploadResultResponse
}

and i use it this way

final class ImagesRepository: BaseRepository {
    
    @Injected(\.imagesApi) private var api
    
    func uploadImage(_ data: Data, mimeType: String) async throws -> String {
        let imagePart = Part(data: data, mimeType: mimeType)
        return try await api.uploadImage(file: imagePart).hash
    }
}

But when i try to run app i get crash, which says:

Can only encode `[String: Part]` with `MultipartEncoder`

I logged it out and it seems like instead of [String: Part] encoder gets [String: RequestBuilder.ContentValue]

Request Cancellation

Right now there isn't a hook to a task to cancel in flight requests, whether async or closure based.

There needs to be some manner of doing so, likely abstracted to the backing networking library.

@JSON has no effect

I have this macro:

extension JSONDecoder {
    static var testing: JSONDecoder {
        Container.shared.jsonDecoder()
    }
}
extension JSONEncoder {
    static var testing: JSONEncoder {
        Container.shared.jsonEncoder()
    }
}

@API
@Mock
@JSON(
    encoder: .testing,
    decoder: .testing
)
@KeyMapping(.snakeCase)
protocol Auth {
    @POST("/register")
    @JSON(
        encoder: .testing,
        decoder: .testing
    )
    func register(body: Body<RegisterRequestBody>) async throws -> TokensResponse
}

But in expansion i see:

struct AuthAPI: Auth {
    private let provider: PapyrusCore.Provider

    init(provider: PapyrusCore.Provider) {
        self.provider = provider
    }

    func register(body: Body<RegisterRequestBody>) async throws -> TokensResponse {
        var req = builder(method: "POST", path: "/register")
        req.requestEncoder = .json(JSONEncoder())
        req.responseDecoder = .json(JSONDecoder())
        req.setBody(body)
        let res = try await provider.request(req)
        try res.validate()
        return try res.decode(TokensResponse.self, using: req.responseDecoder)
    }

    private func builder(method: String, path: String) -> RequestBuilder {
        var req = provider.newBuilder(method: method, path: path)
        req.requestEncoder = .json(JSONEncoder())
        req.responseDecoder = .json(JSONDecoder())
        req.keyMapping = .snakeCase
        return req
    }
}

According to

req.requestEncoder = .json(JSONEncoder())
req.responseDecoder = .json(JSONDecoder())

in both places, It looks like @JSON has no effect on macro

Unable to build project and apply custom JSON encoder and decoder

I've just updated to Papyrus v0.6.5 and get the following error:
image

Macro expansion:
image

Looks like because of this change:
image

BTW, i have JSON applied:

@JSON(
    encoder: Container.shared.jsonEncoder(),
    decoder: Container.shared.jsonDecoder()
)

But according to macro expansion this is not used anywhere or maybe i don't see something?

Request Progress

There's not an easy way to tap into download progress, a useful feature for large downloads or uploads.

Improved Organisation of APIs

Issue:

When it comes to larger APIs, for example, the Google Classroom API, which I work with very often at glassroom, using Papyrus would result in a lot of messy top-level APIs.

How glassroom currently solves this is to declare all the protocols as enums, and all the functionality goes into extensions of those enums as static functions. This means that the definitions look like this:

// Definitions of all the APIs. They're implemented in other files.
public enum GlassRoomAPI {
    public enum GRCourses: GlassRoomAPIProtocol {
        public enum GRAliases: GlassRoomAPIProtocol {}
        public enum GRAnnouncements: GlassRoomAPIProtocol {}
        public enum GRCourseWork: GlassRoomAPIProtocol {
            public enum GRStudentSubmissions: GlassRoomAPIProtocol {}
        }
/* etc */

and calling functions looks more like this:

GlassRoomAPI.GRCourses.GRCourseWork.list(/* parameters here */) { result in
	/*completion here*/ 
}

However, since Papyrus uses protocols for the definitions and the resultant APIs are autogenerated, such organisation cannot be achieved.

@API
protocol GRCourses {
	/*methods here*/

	@API
	protocol GRAliases { // does not compile, as you cannot define other objects within a protocol
		/*methods here*/
    }
}

Suggested solution:

My suggestion is to have an option in @API and @Mock to extend a struct with the new functionality, instead of generating a whole new struct. This would allow for organisation by defining all the structs in a neat manner.

// empty implementations
struct GRCourses {
	struct GRAliases {}
}

@API(in: GRCourses.self)
protocol GRCoursesProtocol {
	/* methods here */
}

@API(in: GRAliases.self)
protocol GRAliasesProtocol {
	/* methods here */
}

/* 
// autogenerated:

extension GRCourses {
	/* implementations here */
}

extension GRAliases {
	/* implementations here */
}
*/

And you would call them this way:

let result = try await GRCourses.GRAliases().listAliases(/* parameters */)

Feature: Ability to decode error response

I'd love to be able to decode an error response, but from what I can see the only way to do this is to opt-out of any built-in response decoding and handle the Response myself entirely.

Use enums with static functions instead of structs for API implementations

Since the autogenerated APIs only contain functions and due to their autogenerated nature cannot contain stored properties (they must be defined in the main body of the struct, which isn't accessible), it is better to have them be static methods in an enum to prevent unnescessary initialisation of a functionally empty struct.

Current usage. You need to initialise a struct to use the function, but the initialised struct is almost immediately deallocated from memory.

@API
protocol Test {
    func getSomething() async throws -> [MyType]
}

/*
// autogenerated struct:
struct TestAPI: Test {
	func getSomething() async throws -> [MyType] { /* Implementation */ }
}
*/

let testApi = TestAPI() // deallocated once the function/program exits
let result = try await testApi.getSomething()
// or
let result = try await TestAPI().getSomething()

Proposed usage. Since the methods are static functions, there is no unnecessary object being allocated.

@API
protocol Test {
    static func getSomething() async throws -> [MyType]
}

/*
// autogenerated static enum:
enum TestAPI: Test {
	static func getSomething() async throws -> [MyType] { /* Implementation */ }
}
*/

let result = try await MyAPI.getSomething()

Note that this would, however, be a breaking change. Developers would have to:

  1. Append a static in front of method declarations in the protocol
  2. Use the API enum directly instead of assigning an instance of the API to a variable and then calling it from the variable.

Path parameter in filename not working

Calling

@GET("/cask/:name.json")
func cask(name: String) async throws -> Cask

with name: "iterm2" tries to request /cask/:name.json?name=iterm2 instead of /cask/iterm2.json, as expected.

Am I misunderstanding the parameter syntax?

Multipart Form Data

Alamofire has built in support for this so the bulk of the work will be locking in how to define each "part" (with custom headers, name, and filename) as a function parameter.

Generalize for Server Usage with `async-http-client`

Would need to abstract the Alamofire specifics and add a separate PapyrusCore target. Then a separate library that provides a async-http-client based driver for use on server.

Investigate "providing" the protocol via that library as well.

Support for Vapor?

I'm working on a Swift project that has a server side, and client side. I'd like to unify the definition of endpoints across both builds.

Something like this would be very useful:

let routesBuilder = ...
let provider = Provider(baseURL: "https://api.example.com/")
let users: Users = UsersAPI(provider: provider)

routesBuilder.addRoutes(from: users)

Failed to resolve dependencies

Hi I have a problem with the latest version

Dependencies could not be resolved because package 'papyrus' is required using a stable-version but 'papyrus' depends on an unstable-version package 'swift-syntax' and 'core' depends on 'papyrus' 0.5.0..

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.