Skip to content

Commit

Permalink
Add aliases for common hints
Browse files Browse the repository at this point in the history
  • Loading branch information
Baccata committed May 12, 2024
1 parent b4bb357 commit 6a6f5ff
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 17 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ You'll typically need the following imports to use the derivation :
```scala
import smithy4s.*
import smithy4s.deriving.{given, *}
import smithy4s.deriving.aliases.* // for syntactically pleasant annotations
import scala.annotation.experimental // the derivation of API uses experimental metaprogramming features, at this time.

import smithy.api.* // if you want to use hints from the official smithy standard library
import alloy.* // if you want to use hints from the alloy library
import scala.annotation.experimental // the derivation of API uses experimental metaprogramming features, at this time.
```


Expand Down Expand Up @@ -281,6 +283,21 @@ operation hello {
* Defaults are supported
* Scaladoc is converted to `@documentation` hints

## More concise annotations

To reduce the verbosity induced by the `hints` annotation, it is possible to define custom annotations that create the responsibility of
creating hints, as such :

```scala
import smithy4s.Hints
import smithy4s.deriving.HintsProvider

case class httpGet(uri: String, status: Int) extends HintsProvider {
def hints = Hints(Http(NonEmptyString("GET"), NonEmptyString(uri), status))
}
```

A few of these more-concise annotations are provided out-of-the-box in the `smithy4s.deriving.aliases` package.

## Difference between smithy4s.deriving.API and and smithy4s.Service

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package smithy4s.deriving.aliases

import smithy4s.Hints
import smithy4s.deriving.HintsProvider
import smithy.api.*
import alloy.Discriminated
import alloy.Untagged
import alloy.SimpleRestJson
import alloy.proto.ProtoIndex

case class httpGet(uri: String, code: Int = 200) extends HintsProvider {
def hints = Hints(Http(NonEmptyString("GET"), NonEmptyString(uri), code))
}

case class httpPost(uri: String, code: Int = 200) extends HintsProvider {
def hints = Hints(Http(NonEmptyString("POST"), NonEmptyString(uri), code))
}

case class httpPatch(uri: String, code: Int = 200) extends HintsProvider {
def hints = Hints(Http(NonEmptyString("PATCH"), NonEmptyString(uri), code))
}

case class httpDelete(uri: String, code: Int = 200) extends HintsProvider {
def hints = Hints(Http(NonEmptyString("DELETE"), NonEmptyString(uri), code))
}

case class httpPut(uri: String, code: Int = 200) extends HintsProvider {
def hints = Hints(Http(NonEmptyString("PUT"), NonEmptyString(uri), code))
}

case class httpLabel() extends HintsProvider {
def hints = Hints(HttpLabel())
}

case class httpQuery(name: String) extends HintsProvider {
def hints = Hints(HttpQuery(name))
}

case class httpHeader(name: String) extends HintsProvider {
def hints = Hints(HttpHeader(name))
}

case class httpPayload() extends HintsProvider {
def hints = Hints(HttpPayload())
}

case class httpError(code: Int) extends HintsProvider {
def hints = Hints(HttpError(code))
}

case class readonly() extends HintsProvider {
def hints = Hints(Readonly())
}

case class idempotent() extends HintsProvider {
def hints = Hints(Idempotent())
}

case class jsonName(name: String) extends HintsProvider {
def hints = Hints(JsonName(name))
}

case class simpleRestJson() extends HintsProvider {
def hints = Hints(SimpleRestJson())
}

case class discriminated(fieldName: String) extends HintsProvider {
def hints = Hints(Discriminated(fieldName))
}

case class untagged() extends HintsProvider {
def hints = Hints(Untagged())
}

case class protoIndex(value: Int) extends HintsProvider {
def hints = Hints(ProtoIndex(value))
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ import smithy4s.{Hint, Hints}
abstract class HintsProvider extends MetaAnnotation {
def hints: Hints
}

/**
* Allows to capture any value the type of which has a `smithy4s.ShapeTag` instance, as a hint.
* The values are typically generated by `Smithy4s` from smithy shapes.
*
* See https://smithy.io/2.0/trait-index.html for the official list of traits. Smithy4s bundled generated data-types
* for each of them.
*
* See https://github.com/disneystreaming/alloy/ for the companion library of smithy traits that Smithy4s also bundles
* generated datatypes for.
*/
case class hints(first: Hint, others: Hint*) extends HintsProvider {
def hints: Hints = Hints.fromSeq(first +: others)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,9 @@ private def altsExpression[T: Type](

private def operationHints(annotations: List[Expr[Any]])(using Quotes): Expr[Hints] =
annotations
.collectFirst { case '{ $smithyAnnotation: HintsProvider } =>
'{ $smithyAnnotation.hints }
}
.getOrElse('{ Hints.empty })
.collect { case '{ $smithyAnnotation: HintsProvider } => '{ $smithyAnnotation.hints }}
.fold('{Hints.empty}){ case (left, right) => '{$left ++ $right}}


private def paramHintsMap(using Quotes)
(annotations: List[List[Expr[Any]]], labels: List[String], paramDocs: Map[String, String], paramSymbols: List[quotes.reflect.Symbol]): Map[String, Expr[Hints]] = {
Expand Down
19 changes: 7 additions & 12 deletions modules/examples/shared/src/main/scala/model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,25 @@ package examples

import smithy4s.*
import smithy4s.deriving.{given, *}
import smithy.api.*
import alloy.*
import smithy4s.deriving.aliases.*
import cats.effect.IO
import scala.annotation.experimental

// Just making it simpler to construct hints
given [A, B](using Bijection[A, B]): Conversion[A, B] with {
def apply(a: A): B = summon[Bijection[A, B]](a)
}

@hints(HttpError(503))
@httpError(503)
case class LocationNotRecognised(errorMessage: String) extends Throwable derives Schema {
override def getMessage(): String = errorMessage
}

@experimental
@hints(SimpleRestJson())
@simpleRestJson
class HelloWorldService() derives API {

@errors[LocationNotRecognised]
@hints(Http(method = "GET", uri = "/hello/{name}"), Readonly())
@readonly
@httpGet("/hello/{name}")
def hello(
@hints(HttpLabel()) name: String,
@hints(HttpQuery("from")) from: Option[String]
@httpLabel() name: String,
@httpQuery("from") from: Option[String]
): IO[String] = from match {
case None => IO(s"Hello $name!")
case Some(loc) if loc.isEmpty() => IO.raiseError(LocationNotRecognised("Empty location"))
Expand Down

0 comments on commit 6a6f5ff

Please sign in to comment.