Giter Site home page Giter Site logo

input-output-hk / armadillo Goto Github PK

View Code? Open in Web Editor NEW
10.0 18.0 2.0 137 KB

Declarative, type-safe json-rpc endpoints library

License: Apache License 2.0

Scala 97.21% Shell 2.36% Nix 0.43%
declarative fs2 http json-rpc openrpc scala tapir uds websocket

armadillo's Introduction

armadillo

CI

Armadillo allows you to easily represent your json-rpc endpoints as regular scala values. These endpoints can be later turn into a http server via tapir or always up-to-date openRpc documentation.

Why another library

We created armadillo because we wanted to have always up-to-date, automatically generated documentation for our api. We looked into tapir as we liked the idea of representing endpoints as pure values but since it is build around http protocol it lacked ability to represent json-rpc routing which from the http perspective is a single dynamic route (the routing is based on the part of the json payload). See softwaremill/tapir#621 for details.

Quick demo

implicit val rpcBlockResponseEncoder: Encoder[GreetingResponse] = deriveEncoder
implicit val rpcBlockResponseDecoder: Decoder[GreetingResponse] = deriveDecoder
implicit val rpcBlockResponseSchema: Schema[GreetingResponse] = Schema.derived

case class GreetingResponse(msg: String)

val helloEndpoint: JsonRpcServerEndpoint[IO] = jsonRpcEndpoint(m"say_hello")
  .in(param[String]("name"))
  .out[GreetingResponse]("greetings")
  .serverLogic[IO] { name =>
    IO(Right(GreetingResponse(s"Hello $name")))
  }

val tapirInterpreter = new TapirInterpreter[IO, Json](new CirceJsonSupport)
val tapirEndpoint = tapirInterpreter.toTapirEndpointUnsafe(List(helloEndpoint))
val routes = Http4sServerInterpreter[IO](Http4sServerOptions.default[IO]).toRoutes(tapirEndpoint)

BlazeServerBuilder[IO]
  .withExecutionContext(ec)
  .bindHttp(8080, "localhost")
  .withHttpApp(Router("/" -> routes).orNotFound)
  .resource
  .flatMap { _ =>
    ArmeriaCatsBackend.resource[IO]()
  }
  .use { client =>
    val request = json"""{"jsonrpc": "2.0", "method": "say_hello", "params": ["kasper"], "id": 1}"""
    SttpClientInterpreter()
      .toClient(tapirEndpoint.endpoint, Some(Uri.apply("localhost", 8080)), client)
      .apply(request.noSpaces)
      .map { response =>
        println(s"Response: $response")
      }
  }
  .unsafeRunSync()

How it works

  1. Using armadillo building blocks describe your jsonrpc endpoints
  2. Attach server logic to created endpoints descriptions
  3. Convert armadillo endpoints to a single tapir endpoint and expose it via one of available http servers
  4. Bonus: automatically generate openRpc documentation and expose it under rpc.discover endpoint

Head over to the examples to see armadillo in action!

Quickstart with sbt

Add the following dependency:

"io.iohk.armadillo" %% "armadillo-core" % "0.0.10"

and IOG nexus repository:

resolvers ++= Seq(
   "IOG Nexus".at("https://nexus.iog.solutions/repository/maven-release/")
),

Quickstart with mill

Add the following dependency:

ivy"io.iohk.armadillo::armadillo-core::0.1.0"

and IOG nexus repository:

def repositoriesTask = T.task { super.repositoriesTask() ++ Seq(
  MavenRepository("https://nexus.iog.solutions/repository/maven-release/")
) }

Modules description

  • core - pure definition of armadillo
  • json
    • circe - support for circe library
    • json4s - support for json4s library
  • server
    • tapir - a server interpreter from armadillo => tapir
    • fs2 - a server interpreter from armadillo => fs2.pipe
  • example - module which pulls all the things together to show the power of armadillo
  • openrpc - interpreter to openrpc
    • model - openrpc structures
    • circe - circe codecs for openrpc structures
    • circeYaml - extension methods to convert openrpc doc into yaml file
  • trace4cats - support for tracing library

Developer notes

Armadillo uses mill as its build tool.

To import project into intellij idea call ./millw mill.scalalib.GenIdea/idea.

If you would like to use bsp instead, call ./millw mill.bsp.BSP/install.

Releases are fully automated using github actions, simply push a new tag to create a new version. Note that mill will try to use the tag name directly as a maven artifact version.

Testing

Weaver exposes a JUnit runner, so tests can be run from Intellij, provided you have JUnit plugin enabled.

To run only selected tests, weaver allows you to tag them with: test("test name".only).

Credits

This library is inspired by another great library - tapir.

Also, big thanks to Adam Warski for reviewing my initial design and patiently answering all of my questions about design choices he took in tapir.

armadillo's People

Contributors

amricko0b avatar dependabot[bot] avatar dleflohic avatar github-actions[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

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

armadillo's Issues

Omitting Optional params in request causes error

Greetings! I'm very happy to find such an awesome JSON-RPC tool for Scala. My thanks and respect to all creators and contributors.

Describe the bug
When optional param omitted in request, server fails to handle it.
It responds with:

{
	"jsonrpc": "2.0",
	"error": {
		"code": -32602,
		"message": "Invalid params"
	},
	"id": "..."
}

Adding Logging interceptor showed that there is a decoding exception: Error(Too many inputs provided,java.lang.RuntimeException: Too many inputs provided).

I suppose, this happens because of actual params check here (ServerInterpreter.scala):

  private def combineDecodeAsObject(in: Vector[JsonRpcIO.Single[_]]): Json.JsonObject[Raw] => DecodeResult[Vector[_]] = { json =>
    val jsonAsMap = json.fields.toMap
    // Seems like the first condition is to blame
    if (jsonAsMap.size >= in.count(_.codec.schema.isOptional) && jsonAsMap.size <= in.size) {
     // ....
    } else {
      val msg = "Too many inputs provided"
      DecodeResult.Error(msg, new RuntimeException(msg))
    }
  }

As far as I understand, when optional params are omitted, the first condition is always false. Maybe, we need just to check that actual ammount of params less or equal to number of inputs?

To Reproduce

  • Describe a test endpoint, like this:
  val testEndpoint: JsonRpcEndpoint[Option[String], Unit, String] =
    jsonRpcEndpoint(m"echo")
      .in(param[Option[String]]("msg1").and(param[String]("msg2")))
      .out[String]("response")
  • Provide some simple logic
  • Export (I use tapir endpoint and http4s interpreter)
  • Send request without param, that is expected to be optional.
{
    "jsonrpc": "2.0",
    "id": "123",
    "method": "echo",
    "params": {
        "msg2": "Two Tiny Little Tigers"
    }
}

Expected behavior
Optional params are treated as None's and cause no errors when decoded.

Additional context
Version: 0.1.0

Check the value of the jsonrpc field

The jsonrpc spec states:

jsonrpc
A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".

Armadillo does not check for that. So if an hypothetical newer version of the spec would appear the server would try to handle queries with some unknown semantics.

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.