Automatically create a scalajs file which will call REST apis defined in a Scala Play! server.

Thomas Flucke d2bf9d2579 Bumped version to be compatible with SBT 1.6. 2 anos atrás
library 77db7b0c04 Formating changes and adding support for JVM. 4 anos atrás
play d2bf9d2579 Bumped version to be compatible with SBT 1.6. 2 anos atrás
plugin 77db7b0c04 Formating changes and adding support for JVM. 4 anos atrás
project d2bf9d2579 Bumped version to be compatible with SBT 1.6. 2 anos atrás
.gitignore 1b8170ed52 Major refactoring and testing 5 anos atrás
README.md b0e623a5ea Added scala3 support. 4 anos atrás
build.sbt d2bf9d2579 Bumped version to be compatible with SBT 1.6. 2 anos atrás
test.sh f84c54a598 Added additional features from TODO list. 5 anos atrás

README.md

Scala REST RPC

What is REST?

Rest is a programming paradigm built around using HTTP methods and status codes to communicate between a client and server.

A RESTful API uses HTTP methods to communicate actions. For example GET is a request for information. PUT means insert or update information. And DELETE removes information.

The path describes the data to operate on. For example /user/123/address might refer to the address of a user with the id 123.

The body of a request contains the details about the request. For example the body of the request PUT /user/123/address might contain a new address to associate with the user.

The response body will often contain the result of the transaction or an error message.

What is RPC?

RPC is a programming paradigm where a program calls a function which then runs on another system. From the client program's perspective, the API looks just like a normal function call. But the arguments to the function are actually sent to the server which the preforms the function and sends the result back to the client.

What is REST RPC?

REST RPC is a plugin which creates an RPC interface over a RESTful one.

For example, say a program has an API PUT /user/:id/address which maps to the function addUserAddress(id: Long, addr: Address): User. The client program using REST RPC could then make this function call: addUserAddress(123)(myNewAddr). The RPC layer would then transform that function call into this HTTP request:

PUT /user/123/address HTTP/1.1
Accept: application/json

{
   "line1": "123 Home Ave.",
   "line2": null,
   "zip_code": 12345,
   "city": "St. Sample",
   "state": "CA"
}

And then transform the server's response into a strongly typed value. All without writing any HTTP manipulation.

Why use REST RPC

Several reasons:

  • Type safety between client and server
  • Cleanliness of client code
  • Simplifies tracking the relationships between front-end and back-end
  • Flexibility in renaming APIs during development

How do I use REST RPC?

First, add the plugin to SBT by inserting this line into project/plugins.sbt:

addSbtPlugin("com.tflucke"       % "sbt-rest-rpc"            % "0.1.0")

Second, add the plugin to your project in build.sbt:

enablePlugins(RestRpc)

/* Or if you are using subprojects */

client.enablePlugins(RestRpc)

Finally, add the API file and parser as an input in build.sbt:

client.settings(
    Compile / apiDefinitions += PlayEndpointFile(server)
)

Examples

Sample projects can be found in the plugin/src/sbt-test/ directory.

FAQ

What if I don't use the play! framework for my server?

You can still use this plugin! You have two options which will work:

One is to create a routes file similar to the one play! framework uses. Even if it is not used by the server, REST RPC can still parse it to create the wrapper APIs.

Another option is to write an APIParser for whichever tool you use to create your APIs. If you feel like you have something useful and robust, feel free to share and we might add it to the plugin for other users.

What if I my client communicates with multiple servers?

This is not a problem. Add a config file containing a list of APIs for each of the servers or one file with a complete list. REST RPC will generate wrapper functions for each of the APIs for each of the servers so long as the servers do not have two functions with the same signature. (We recommend putting each server in a different package to guarantee that each function will be unique.)

Why does the client have to call the function twice?

Because we wanted the client to be able to access underlying HTTP properties if it became useful. For example, instead of calling the API, a program may want to acquire the URL like this:

println(addUserAddress(123).url)

Outputs:

/user/123/address

Additionally, the second call allows the user to specify HTTP arguments separately from RPC arguments. For example:

addUserAddress(123)(myNewAddr, timeout = 5)

This clear separation between the RPC and HTTP helps both readability and reduce namespace collisions (After all, a user might want an RPC parameter named timeout).

It also allows the client to call the same API multiple times without re-computing the URL. This is useful for APIs which have windowed results.

Finally, it allows the client interface to have a separate parameter for the API body. This is useful because it eliminates the possibility of collisions and allows the client code to call the same interface with multiple bodies.

Will this plugin support other frameworks besides the play! framework?

While this plugin in intended to be flexible, there are no immediate plans to support other frameworks at this time.

But we will still happily accept support from the community in this matter.

Using this repository

Publishing

TODO

Running Tests

sbt test will run the unit tests for this project.

sbt scripted will run the integration tests for this project. Be warned that this will open browser windows. We recommend using ./test.sh instead.

./test.sh will run all the tests. If Xvfb is available, it will run the tests in a virtual X11 session to prevent unnecessary disruption of workflow and allow the script to run on a headless server.

TODO:

  • Java support
  • Clean support for other frameworks besides play!
  • Upickle serializer
  • Aliasing RPC function names
  • Overloading RPC function arguments
  • Source file parsers
  • Support NodeJs (Using http.request instead of XMLhttprequest)