Deriving CLIs
- The Smithy4s Decline module provides the capability to derive a Decline Cli for your service.
- The cli generated will be in the form of a Command[F[Unit]] where F is the effect type of your service.
- This module is written in Tagless Final style and requires an
F[_]
for which there is an instance of cats.MonadThrow - Let's revisit our HelloWorld smithy definition from the Quickstart
namespace smithy4s.hello
use alloy#simpleRestJson
@simpleRestJson
service HelloWorldService {
version: "1.0.0",
operations: [Hello]
}
@http(method: "POST", uri: "/{name}", code: 200)
operation Hello {
input: Person,
output: Greeting
}
structure Person {
@httpLabel
@required
name: String,
@httpQuery("town")
town: String
}
structure Greeting {
@required
message: String
}
Let's implement the HelloWorld service, we will use cats.effect.IO
for our effect type.
import smithy4s.example.hello._
import cats.effect.IO
object HelloWorldServiceInstance{
val simple = new HelloWorldService[IO]{
def hello(name: String, town: Option[String]): IO[Greeting] = {
IO {
Greeting(s"hello $name , who hails from $town")
}
}
}
}
- Now Using the
decline
module from Smithy4s we can wrap the service instance in an instance of aSmithy4sCli
.- The
Smithy4sCli
allows the customization of the Opts and stdin/stdout/stderr handling
- The
- There is a convenient helper method
Smithy4sCli.standalone
to make it easier to construct Cli's using defaults. - To utilize the helper method, wrap the service in an instance of
Opts
and pass it intoSmithy4sCli.standalone
- there are 2 methods available on the
Smithy4sCli
instanceopts
which will provide anOpts[F[Unit]]
for the servicecommand
which will provide aCommand[F[Unit]]
for the service. This uses defaults from the Smithy spec- command name will use the service name
- if documentation comments are available on the service , they will be used as the command help text
import com.monovore.decline._
import smithy4s.decline.Smithy4sCli
val serviceWrappedInOpts = Opts(HelloWorldServiceInstance.simple)
val helloCommand: Command[IO[Unit]] = Smithy4sCli.standalone(serviceWrappedInOpts).command
helloCommand
is now a runnableCommand
that can parse command line args and returns an IO[Unit]- We can implement a CLI that will run the command and print the result to stdout
import smithy4s.decline.Smithy4sCli
import cats.effect._
import com.monovore.decline._
import com.monovore.decline.effect.CommandIOApp
object app extends IOApp {
override def run(args: List[String]) = {
val helloCommand: Command[IO[ExitCode]] = Smithy4sCli
.standalone(Opts(HelloWorldServiceInstance.simple))
.command.map(_.redeem(_ => ExitCode.Error, _ => ExitCode.Success))
CommandIOApp.run(helloCommand, args)
}
}
- the command will provide the following interface
Usage: hello-world-service hello [--output <output>] <name> [<town>]
HTTP POST /{name}
Options and flags:
--help
Display this help text.
--output <output>
Output mode
Smithy to Decline mappings
- The Service name will be used to generate the top level Command
- All operations will be mapped to subcommands
- The Input structure is flattened to top level fields with no nesting
- All Primitives are mapped to a Positional
decline
Argument , with the following exceptions.- Boolean fields are mapped to a
decline
Flag - Blobs are mapped to take in either stdin(by passing in
-
) or a full file path and will produce a Byte array - Timestamps are parsed using TimestampFormat.DATE_TIME by default, but @timestampFormat can be used to customize that.
- Boolean fields are mapped to a
- Lists and recursive types
- a top level list is converted to a repeated positional argument (or flag in case of Boolean)
- All recursive types expect json to be passed in.
- How is Nesting handled
- all nested fields are converted to Options
- a nested list is converted to expect a json i.e. List[String] would expect
'["foo","baz","qux"]'
as a decline Option- when the nested list contains blobs , it will expect a json of base64 encoded strings
- All Primitives are mapped to a Positional
- Help
- Documentation is added to every field, operation and service - if available. For HTTP operations a path template is provided as well.
Missing Features
- Collision Handling for un-nesting of nested fields