├── .gitignore
├── server
├── public
│ ├── stylesheets
│ │ └── main.css
│ └── images
│ │ └── favicon.png
├── conf
│ ├── messages
│ ├── application.conf
│ ├── heroku.conf
│ ├── routes
│ └── logback.xml
└── app
│ ├── views
│ ├── index.scala.html
│ └── main.scala.html
│ ├── controllers
│ ├── RootController.scala
│ └── Users.scala
│ ├── loader
│ ├── package.scala
│ └── AppLoader.scala
│ ├── users
│ └── package.scala
│ └── commons
│ └── package.scala
├── project
├── build.properties
└── plugins.sbt
├── .g8
└── form
│ ├── default.properties
│ ├── app
│ ├── views
│ │ └── $model__camel$
│ │ │ └── form.scala.html
│ └── controllers
│ │ └── $model__Camel$Controller.scala
│ └── test
│ └── controllers
│ └── $model__Camel$ControllerSpec.scala
├── shared
└── src
│ └── main
│ └── scala
│ └── com
│ └── example
│ └── playscalajs
│ └── shared
│ └── SharedMessages.scala
├── README.md
├── macros
└── src
│ ├── main
│ └── scala
│ │ └── zio
│ │ └── macros
│ │ ├── accessible.scala
│ │ └── AccessibleMacro.scala
│ └── test
│ └── scala
│ └── zio
│ └── macros
│ ├── AccessibleSpecPlain.scala
│ └── AccessibleSpec.scala
└── client
└── src
└── main
└── scala
└── com
└── example
└── playscalajs
└── ScalaJSExample.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | logs/
3 |
--------------------------------------------------------------------------------
/server/public/stylesheets/main.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.4.5
2 |
--------------------------------------------------------------------------------
/server/conf/messages:
--------------------------------------------------------------------------------
1 | # https://www.playframework.com/documentation/latest/ScalaI18N
2 |
--------------------------------------------------------------------------------
/.g8/form/default.properties:
--------------------------------------------------------------------------------
1 | description = Generates a Controller with form handling
2 | model = user
3 |
--------------------------------------------------------------------------------
/server/public/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DmytroOrlov/play-zio-distage/HEAD/server/public/images/favicon.png
--------------------------------------------------------------------------------
/server/conf/application.conf:
--------------------------------------------------------------------------------
1 | play.application.loader = "loader.AppLoader"
2 |
3 | play.assets {
4 | path = "/public"
5 | urlPrefix = "/assets"
6 | }
7 |
--------------------------------------------------------------------------------
/shared/src/main/scala/com/example/playscalajs/shared/SharedMessages.scala:
--------------------------------------------------------------------------------
1 | package com.example.playscalajs.shared
2 |
3 | object SharedMessages {
4 | def itWorks = "It works!"
5 | }
6 |
--------------------------------------------------------------------------------
/server/conf/heroku.conf:
--------------------------------------------------------------------------------
1 | include "application"
2 |
3 | play.http.secret.key="kUNSMzxg/Play and Scala.js share a same message
5 |
6 | - Play shouts out: @message
7 | - Scala.js shouts out:
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/server/app/controllers/RootController.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import com.example.playscalajs.shared.SharedMessages
4 | import play.api.mvc.BaseController
5 |
6 | trait RootController extends BaseController {
7 |
8 | def index = Action {
9 | Ok(views.html.index(SharedMessages.itWorks))
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/server/app/loader/package.scala:
--------------------------------------------------------------------------------
1 | import izumi.distage.model.definition.DIResource.DIResourceBase
2 | import izumi.distage.model.effect.DIEffect
3 |
4 | package object loader {
5 |
6 | implicit final class DIResourceNoRelease[F[_], A](private val resource: DIResourceBase[F, A]) extends AnyVal {
7 | def unsafeNoRelease[B](implicit F: DIEffect[F]): F[(A, F[Unit])] = {
8 | F.flatMap(resource.acquire)(a => F.maybeSuspend(resource.extract(a) -> resource.release(a)))
9 | }
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ZIO / DIstage with playframework
2 |
3 | ## Start the app
4 |
5 | ```
6 | sbt '~server/run'
7 | ```
8 |
9 | ## Use the app
10 |
11 | ```bash
12 | curl -XPOST http://localhost:9000/users -H 'Content-Type:application/json' -d '{"name":"test"}'
13 | ```
14 |
15 | ```bash
16 | curl http://localhost:9000/users
17 | ```
18 |
19 | ```bash
20 | curl -XPUT http://localhost:9000/users/da8b2cd6-a88c-4a54-81b8-eeb7ce9075c5 -H 'Content-Type:application/json' -d '{"id":"da8b2cd6-a88c-4a54-81b8-eeb7ce9075c5","name":"test2"}'
21 | ```
22 |
--------------------------------------------------------------------------------
/.g8/form/app/views/$model__camel$/form.scala.html:
--------------------------------------------------------------------------------
1 | @($model;format="camel"$Form: Form[$model;format="Camel"$Data])(implicit request: MessagesRequestHeader)
2 |
3 | $model;format="camel"$ form
4 |
5 | @request.flash.get("success").getOrElse("")
6 |
7 | @helper.form(action = routes.$model;format="Camel"$Controller.$model;format="camel"$Post()) {
8 | @helper.CSRF.formField
9 | @helper.inputText($model;format="camel"$Form("name"))
10 | @helper.inputText($model;format="camel"$Form("age"))
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/server/app/views/main.scala.html:
--------------------------------------------------------------------------------
1 | @(title: String)(content: Html)
2 |
3 |
4 |
5 |
6 |
7 | @title
8 |
9 |
10 |
11 |
12 |
13 | @content
14 | @scalajs.html.scripts("client", routes.Assets.versioned(_).toString, name => getClass.getResource(s"/public/$name") != null)
15 |
16 |
17 |
--------------------------------------------------------------------------------
/server/conf/routes:
--------------------------------------------------------------------------------
1 | # Routes
2 | # This file defines all application routes (Higher priority routes first)
3 | # ~~~~
4 |
5 | # Home page
6 | GET / controllers.RootController.index
7 | GET /users controllers.UserController.list
8 | POST /users controllers.UserController.create
9 | GET /users/:id controllers.UserController.getById(id)
10 | PUT /users/:id controllers.UserController.update(id)
11 |
12 | # Prefix must match `play.assets.urlPrefix`
13 | GET /assets/*file controllers.Assets.at(file)
14 | GET /versionedAssets/*file controllers.Assets.versioned(path="/public", file: Asset)
15 |
--------------------------------------------------------------------------------
/macros/src/main/scala/zio/macros/accessible.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 John A. De Goes and the ZIO Contributors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package zio.macros
17 |
18 | import scala.annotation.{ compileTimeOnly, StaticAnnotation }
19 |
20 | @compileTimeOnly("enable macro paradise to expand macro annotations")
21 | class accessible[A] extends StaticAnnotation {
22 | def macroTransform(annottees: Any*): Any = macro AccessibleMacro.apply
23 | }
24 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | // Play
2 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.2")
3 | addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.13.1")
4 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2")
5 |
6 | // ScalaJS
7 | // Comment to get more information during initialization
8 | logLevel := Level.Warn
9 |
10 | // Resolvers
11 | resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/"
12 |
13 | // Sbt plugins
14 |
15 | // Use Scala.js v1.x
16 | addSbtPlugin("com.vmunier" % "sbt-web-scalajs" % "1.1.0")
17 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.4.0")
18 | // If you prefer using Scala.js v0.6.x, uncomment the following plugins instead:
19 | // addSbtPlugin("com.vmunier" % "sbt-web-scalajs" % "1.1.0-0.6")
20 | // addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.33")
21 |
22 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0")
23 | addSbtPlugin("com.typesafe.sbt" % "sbt-gzip" % "1.0.2")
24 | addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.1.4")
25 |
--------------------------------------------------------------------------------
/server/conf/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ${application.home:-.}/logs/application.log
8 |
9 | UTF-8
10 |
11 | %d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{36}) %magenta(%X{akkaSource}) %msg%n
12 |
13 |
14 |
15 |
16 |
17 | true
18 |
19 | UTF-8
20 |
21 | %d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{36}) %magenta(%X{akkaSource}) %msg%n
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/server/app/loader/AppLoader.scala:
--------------------------------------------------------------------------------
1 | package loader
2 |
3 | import commons._
4 | import controllers.{AssetsComponents, UserController, Users}
5 | import distage.{Injector, ModuleDef}
6 | import play.api.ApplicationLoader.Context
7 | import play.api._
8 | import play.api.mvc.{ControllerComponents, EssentialFilter}
9 | import play.api.routing.Router
10 | import router.Routes
11 | import users.UserRepository
12 | import zio._
13 |
14 | class AppLoader extends ApplicationLoader {
15 |
16 | def load(context: Context): Application = {
17 | LoggerConfigurator(context.environment.classLoader).foreach {
18 | _.configure(context.environment, context.initialConfiguration, Map.empty)
19 | }
20 | new BuiltInComponentsFromContext(context) with AssetsComponents {
21 | lazy val rts = Runtime.default
22 |
23 | val definition = new ModuleDef {
24 | make[Runtime[Any]].from(rts)
25 | make[ControllerComponents].from(controllerComponents)
26 | make[Logger].fromValue(Logger("users"))
27 | make[PlayLogger].from(PlayLogger.make _)
28 | make[UserRepository].fromHas(UserRepository.make)
29 | make[Users].fromHas(Users.make)
30 | make[UserController]
31 | }
32 | val (controllers, release) = rts.unsafeRun(
33 | Injector()
34 | .produceGetF[Task, UserController](definition)
35 | .unsafeNoRelease
36 | )
37 |
38 | applicationLifecycle.addStopHook(() => rts.unsafeRunToFuture(release))
39 |
40 | override def router: Router =
41 | new Routes(
42 | httpErrorHandler,
43 | controllers,
44 | controllers,
45 | assets
46 | )
47 |
48 | override def httpFilters: Seq[EssentialFilter] = Seq()
49 | }.application
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/.g8/form/app/controllers/$model__Camel$Controller.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import javax.inject._
4 | import play.api.mvc._
5 |
6 | import play.api.data._
7 | import play.api.data.Forms._
8 |
9 | case class $model;format="Camel"$Data(name: String, age: Int)
10 |
11 | // NOTE: Add the following to conf/routes to enable compilation of this class:
12 | /*
13 | GET /$model;format="camel"$ controllers.$model;format="Camel"$Controller.$model;format="camel"$Get
14 | POST /$model;format="camel"$ controllers.$model;format="Camel"$Controller.$model;format="camel"$Post
15 | */
16 |
17 | /**
18 | * $model;format="Camel"$ form controller for Play Scala
19 | */
20 | class $model;format="Camel"$Controller @Inject()(mcc: MessagesControllerComponents) extends MessagesAbstractController(mcc) {
21 |
22 | val $model;format="camel"$Form = Form(
23 | mapping(
24 | "name" -> text,
25 | "age" -> number
26 | )($model;format="Camel"$Data.apply)($model;format="Camel"$Data.unapply)
27 | )
28 |
29 | def $model;format="camel"$Get() = Action { implicit request: MessagesRequest[AnyContent] =>
30 | Ok(views.html.$model;format="camel"$.form($model;format="camel"$Form))
31 | }
32 |
33 | def $model;format="camel"$Post() = Action { implicit request: MessagesRequest[AnyContent] =>
34 | $model;format="camel"$Form.bindFromRequest.fold(
35 | formWithErrors => {
36 | // binding failure, you retrieve the form containing errors:
37 | BadRequest(views.html.$model;format="camel"$.form(formWithErrors))
38 | },
39 | $model;format="camel"$Data => {
40 | /* binding success, you get the actual value. */
41 | /* flashing uses a short lived cookie */
42 | Redirect(routes.$model;format="Camel"$Controller.$model;format="camel"$Get()).flashing("success" -> ("Successful " + $model;format="camel"$Data.toString))
43 | }
44 | )
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/server/app/controllers/Users.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import cats.syntax.either._
4 | import commons._
5 | import play.api.libs.json._
6 | import play.api.mvc._
7 | import users._
8 | import zio._
9 |
10 | import java.util.UUID
11 |
12 | case class UserDto(name: String)
13 |
14 | object UserDto {
15 | implicit val format = Json.format[UserDto]
16 | }
17 |
18 | trait Users {
19 | def list: Task[List[User]]
20 |
21 | def create(user: UserDto): Task[UserDto]
22 |
23 | def getById(id: String): Task[Option[User]]
24 |
25 | def update(id: String, user: User): Task[Either[String, User]]
26 | }
27 |
28 | object Users {
29 | val make =
30 | for {
31 | env <- ZIO.environment[Has[PlayLogger] with Has[UserRepository]]
32 | } yield new Users {
33 | val list = (for {
34 | _ <- PlayLogger.debug(s"Listing all users")
35 | users <- UserRepository.list
36 | } yield users)
37 | .provide(env)
38 |
39 | def create(user: UserDto) =
40 | (for {
41 | _ <- PlayLogger.debug(s"Creating user")
42 | id <- Task(UUID.randomUUID().toString)
43 | _ <- UserRepository.save(User(id, user.name))
44 | } yield user)
45 | .provide(env)
46 |
47 | def getById(id: String) =
48 | (for {
49 | _ <- PlayLogger.debug(s"Looking for user $id")
50 | maybeUser <- UserRepository.getById(id)
51 | } yield maybeUser)
52 | .provide(env)
53 |
54 | def update(id: String, user: User) =
55 | (for {
56 | _ <- PlayLogger.debug(s"Updating user $id")
57 | mayBeUser <- UserRepository.getById(id)
58 | res <- mayBeUser.fold(Task(s"User $id should exists".asLeft[User])) { _ =>
59 | (for {
60 | _ <- UserRepository.delete(id)
61 | _ <- UserRepository.save(User(id, user.name))
62 | } yield user.asRight[String])
63 | .provide(env)
64 | }
65 | } yield res)
66 | .provide(env)
67 | }
68 | }
69 |
70 | class UserController(
71 | controllerComponents: ControllerComponents,
72 | users: Users,
73 | )(implicit val rts: Runtime[Any]) extends AbstractController(controllerComponents) with RootController {
74 | val list = Action.fromTask(_ => users.list)
75 |
76 | val create = Action.fromTask(parse.json[UserDto]) { req =>
77 | users.create(req.body)
78 | }
79 |
80 | def getById(id: String) =
81 | Action.fromTask(s"No user for $id") { _ =>
82 | users.getById(id)
83 | }
84 |
85 | def update(id: String) =
86 | Action.fromTaskEither(parse.json[User]) { req =>
87 | users.update(id, req.body)
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/.g8/form/test/controllers/$model__Camel$ControllerSpec.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import play.api.mvc._
4 | import play.api.i18n._
5 | import org.scalatestplus.play._
6 | import org.scalatestplus.play.guice.GuiceOneAppPerTest
7 | import play.api.http.FileMimeTypes
8 | import play.api.test._
9 | import play.api.test.Helpers._
10 | import play.api.test.CSRFTokenHelper._
11 |
12 | import scala.concurrent.ExecutionContext
13 |
14 | /**
15 | * $model;format="Camel"$ form controller specs
16 | */
17 | class $model;format="Camel"$ControllerSpec extends PlaySpec with GuiceOneAppPerTest with Injecting {
18 |
19 | // Provide stubs for components based off Helpers.stubControllerComponents()
20 | class StubComponents(cc:ControllerComponents = stubControllerComponents()) extends MessagesControllerComponents {
21 | override val parsers: PlayBodyParsers = cc.parsers
22 | override val messagesApi: MessagesApi = cc.messagesApi
23 | override val langs: Langs = cc.langs
24 | override val fileMimeTypes: FileMimeTypes = cc.fileMimeTypes
25 | override val executionContext: ExecutionContext = cc.executionContext
26 | override val actionBuilder: ActionBuilder[Request, AnyContent] = cc.actionBuilder
27 | override val messagesActionBuilder: MessagesActionBuilder = new DefaultMessagesActionBuilderImpl(parsers.default, messagesApi)(executionContext)
28 | }
29 |
30 | "$model;format="Camel"$Controller GET" should {
31 |
32 | "render the index page from a new instance of controller" in {
33 | val controller = new $model;format="Camel"$Controller(new StubComponents())
34 | val request = FakeRequest().withCSRFToken
35 | val home = controller.$model;format="camel"$Get().apply(request)
36 |
37 | status(home) mustBe OK
38 | contentType(home) mustBe Some("text/html")
39 | }
40 |
41 | "render the index page from the application" in {
42 | val controller = inject[$model;format="Camel"$Controller]
43 | val request = FakeRequest().withCSRFToken
44 | val home = controller.$model;format="camel"$Get().apply(request)
45 |
46 | status(home) mustBe OK
47 | contentType(home) mustBe Some("text/html")
48 | }
49 |
50 | "render the index page from the router" in {
51 | val request = CSRFTokenHelper.addCSRFToken(FakeRequest(GET, "/$model;format="camel"$"))
52 | val home = route(app, request).get
53 |
54 | status(home) mustBe OK
55 | contentType(home) mustBe Some("text/html")
56 | }
57 | }
58 |
59 | "$model;format="Camel"$Controller POST" should {
60 | "process form" in {
61 | val request = {
62 | FakeRequest(POST, "/$model;format="camel"$")
63 | .withFormUrlEncodedBody("name" -> "play", "age" -> "4")
64 | }
65 | val home = route(app, request).get
66 |
67 | status(home) mustBe SEE_OTHER
68 | }
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/server/app/users/package.scala:
--------------------------------------------------------------------------------
1 | import java.io.File
2 |
3 | import org.iq80.leveldb.impl.Iq80DBFactory.{asString, bytes, factory}
4 | import org.iq80.leveldb.{DB, DBIterator, Options}
5 | import play.api.libs.json.{JsError, Json}
6 | import zio._
7 | import zio.macros.accessible
8 | import commons._
9 |
10 | package object users {
11 |
12 | object User {
13 | implicit val format = Json.format[User]
14 | }
15 |
16 | case class User(id: String, name: String)
17 |
18 | @accessible
19 | trait UserRepository {
20 | def list: Task[List[User]]
21 |
22 | def getById(id: String): Task[Option[User]]
23 |
24 | def save(user: User): Task[Unit]
25 |
26 | def delete(id: String): Task[Unit]
27 | }
28 |
29 | object UserRepository {
30 | val make = for {
31 | _ <- PlayLogger.info("Opening level DB at target/leveldb").toManaged_
32 | res <- Task(
33 | new LevelDbUserRepository(
34 | factory.open(
35 | new File("target/leveldb").getAbsoluteFile,
36 | new Options().createIfMissing(true)
37 | )
38 | )
39 | ).toManaged { repo =>
40 | PlayLogger.info("Closing level DB at target/leveldb") *> Task(repo.db.close()).ignore
41 | }
42 | } yield res
43 |
44 | class LevelDbUserRepository(val db: DB) extends UserRepository {
45 |
46 | def parseJson(str: String): Task[User] =
47 | Task(Json.parse(str)).flatMap { json =>
48 | json
49 | .validate[User]
50 | .fold(
51 | err => Task.fail(new RuntimeException(s"Error parsing user: ${Json.stringify(JsError.toJson(err))}")),
52 | ok => Task.succeed(ok)
53 | )
54 | }
55 |
56 | override def list: Task[List[User]] =
57 | listAll(db.iterator())
58 |
59 | def listAll(iterator: DBIterator): Task[List[User]] =
60 | for {
61 | hasNext <- Task(iterator.hasNext)
62 | value <- if (hasNext) {
63 | for {
64 | nextValue <- Task(iterator.next())
65 | user <- parseJson(asString(nextValue.getValue))
66 | n <- listAll(iterator)
67 | } yield user :: n
68 | } else {
69 | Task(List.empty[User])
70 | }
71 | } yield value
72 |
73 | override def getById(id: String): Task[Option[User]] =
74 | for {
75 | stringValue <- Task {
76 | asString(db.get(bytes(id)))
77 | }
78 | user <- if (stringValue != null) {
79 | parseJson(stringValue).map(Option.apply)
80 | } else Task.succeed(Option.empty[User])
81 | } yield user
82 |
83 | override def save(user: User): Task[Unit] =
84 | Task {
85 | val stringUser = Json.stringify(Json.toJson(user))
86 | db.put(bytes(user.id), bytes(stringUser))
87 | }
88 |
89 | override def delete(id: String): Task[Unit] = Task(db.delete(bytes(id)))
90 | }
91 |
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/client/src/main/scala/com/example/playscalajs/ScalaJSExample.scala:
--------------------------------------------------------------------------------
1 | package com.example.playscalajs
2 |
3 | import com.example.playscalajs.shared.SharedMessages
4 | import org.scalajs.dom
5 | import org.scalajs.dom.Node
6 | import zio._
7 |
8 | import scala.scalajs.js
9 | import scala.scalajs.js.annotation.JSGlobal
10 |
11 | @js.native
12 | @JSGlobal("THREE.Scene")
13 | class Scene extends js.Object {
14 | def add(obj: js.Object): Unit = js.native
15 | }
16 |
17 | @js.native
18 | @JSGlobal("THREE.PerspectiveCamera")
19 | class PerspectiveCamera(a: Double, b: Double, c: Double, d: Double) extends Camera {
20 | var position: Vector3 = js.native
21 | }
22 |
23 | @js.native
24 | @JSGlobal("THREE.WebGLRenderer")
25 | class WebGLRenderer(params: js.Dynamic) extends js.Object {
26 | def setSize(width: Double, height: Double, updateStyle: Boolean = js.native): Unit = js.native
27 |
28 | def render(scene: Scene, camera: Camera, renderTarget: RenderTarget = js.native, forceClear: Boolean = js.native): Unit = js.native
29 |
30 | var domElement: Node = js.native
31 | }
32 |
33 | @js.native
34 | @JSGlobal("THREE.Camera")
35 | class Camera extends js.Object
36 |
37 | trait RenderTarget extends js.Object
38 |
39 | @js.native
40 | @JSGlobal("THREE.Vector3")
41 | class Vector3(var x: Double, var y: Double, var z: Double) extends js.Object
42 |
43 | @js.native
44 | @JSGlobal("THREE.Mesh")
45 | class Mesh(geometry: Geometry, material: Material) extends js.Object {
46 | var rotation: Euler = js.native
47 | }
48 |
49 | class Geometry extends js.Object
50 |
51 | class Material extends js.Object
52 |
53 | @js.native
54 | @JSGlobal("THREE.BoxGeometry")
55 | class BoxGeometry(
56 | width: Double,
57 | height: Double,
58 | depth: Double,
59 | widthSegments: Double = js.native,
60 | heightSegments: Double = js.native,
61 | depthSegments: Double = js.native,
62 | ) extends Geometry
63 |
64 | @js.native
65 | @JSGlobal("THREE.MeshBasicMaterial")
66 | class MeshBasicMaterial(params: js.Dynamic) extends Material
67 |
68 | @js.native
69 | @JSGlobal("THREE.Euler")
70 | class Euler extends js.Object {
71 | var x: Double = js.native
72 | var y: Double = js.native
73 | }
74 |
75 | object ScalaJSExample extends App {
76 | def run(args: List[String]) = {
77 | IO {
78 | val scene = new Scene()
79 | val camera = new PerspectiveCamera(75, dom.window.innerWidth / dom.window.innerHeight, 0.1, 1000)
80 | val renderer = new WebGLRenderer(js.Dynamic.literal())
81 | renderer.setSize(dom.window.innerWidth, dom.window.innerHeight)
82 | dom.document.body.appendChild(renderer.domElement)
83 | camera.position.z = 5
84 |
85 | def createCube(side: Double): Mesh = {
86 | val geometry = new BoxGeometry(side, side, side)
87 | val material = new MeshBasicMaterial(js.Dynamic.literal(color = 0x00ff00, wireframe = true))
88 | val cube = new Mesh(geometry, material)
89 | cube
90 | }
91 |
92 | val cube = createCube(3)
93 | scene.add(cube)
94 |
95 | def renderLoop(timestamp: Double): Unit = {
96 | dom.window.requestAnimationFrame(renderLoop _)
97 | cube.rotation.x += .03
98 | cube.rotation.y += .1
99 | renderer.render(scene, camera)
100 | }
101 |
102 | renderLoop(System.currentTimeMillis())
103 | dom.document.getElementById("scalajsShoutOut").textContent = SharedMessages.itWorks
104 | }.exitCode
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/server/app/commons/package.scala:
--------------------------------------------------------------------------------
1 | import play.api.libs.json.{Json, Writes}
2 | import play.api.mvc.Results._
3 | import play.api.mvc.{Action, ActionBuilder, BodyParser, Result}
4 | import play.api.{Logger, MarkerContext}
5 | import zio.macros.accessible
6 | import zio.{RIO, Runtime, Task, UIO, ZIO}
7 |
8 | package object commons {
9 |
10 | implicit class ActionBuilderOps[+R[_], B](val ab: ActionBuilder[R, B]) extends AnyVal {
11 | def asyncTask[Env](cb: R[B] => RIO[Env, Result])(implicit rts: Runtime[Env]): Action[B] =
12 | ab.async { c =>
13 | val zio = cb(c)
14 | rts.unsafeRunToFuture(zio)
15 | }
16 |
17 | def asyncTask[Env](
18 | bp: BodyParser[B]
19 | )(cb: R[B] => RIO[Env, Result])(implicit rts: Runtime[Env]): Action[B] =
20 | ab.async(bp) { c =>
21 | val zio = cb(c)
22 | rts.unsafeRunToFuture(zio)
23 | }
24 |
25 | def asyncZio[Env](cb: R[B] => ZIO[Env, Result, Result])(implicit rts: Runtime[Env]): Action[B] =
26 | ab.async { c =>
27 | val zio = cb(c).either.map(_.merge)
28 | rts.unsafeRunToFuture(zio)
29 | }
30 |
31 | def asyncZio[A, Env](
32 | bp: BodyParser[A]
33 | )(cb: R[A] => ZIO[Env, Result, Result])(implicit rts: Runtime[Env]): Action[A] =
34 | ab.async[A](bp) { c =>
35 | val zio = cb(c).either.map(_.merge)
36 | rts.unsafeRunToFuture(zio)
37 | }
38 |
39 | def fromTask[A: Writes](cb: R[B] => Task[A])(implicit rts: Runtime[Any]): Action[B] =
40 | ab.async { c =>
41 | val zio = cb(c).bimap(
42 | e => InternalServerError(s"$e"),
43 | a => Ok(Json.toJson(a))
44 | ).either
45 | .map(_.merge)
46 | rts.unsafeRunToFuture(zio)
47 | }
48 |
49 | def fromTask[A: Writes, E](e: => String)(cb: R[B] => Task[Option[A]])(implicit rts: Runtime[Any]): Action[B] =
50 | ab.async { c =>
51 | val zio = cb(c).mapError(e => InternalServerError(s"$e"))
52 | .flatMap { maybeA =>
53 | ZIO.fromOption(maybeA).bimap(
54 | _ => NotFound(Json.obj("message" -> e)),
55 | a => Ok(Json.toJson(a))
56 | )
57 | }.either
58 | .map(_.merge)
59 | rts.unsafeRunToFuture(zio)
60 | }
61 |
62 | def fromTaskEither[A: Writes, C](
63 | bp: BodyParser[C]
64 | )(cb: R[C] => Task[Either[String, A]])(implicit rts: Runtime[Any]): Action[C] =
65 | ab.async(bp) { c =>
66 | val zio = cb(c).mapError(e => InternalServerError(s"$e"))
67 | .flatMap(
68 | either => ZIO.fromEither(either).bimap(
69 | e => NotFound(Json.obj("message" -> e)),
70 | a => Ok(Json.toJson(a))
71 | )
72 | ).either
73 | .map(_.merge)
74 | rts.unsafeRunToFuture(zio)
75 | }
76 |
77 | def fromTask[A: Writes, C](
78 | bp: BodyParser[C]
79 | )(cb: R[C] => Task[A])(implicit rts: Runtime[Any]): Action[C] =
80 | ab.async(bp) { c =>
81 | val zio = cb(c).bimap(
82 | e => InternalServerError(s"$e"),
83 | a => Ok(Json.toJson(a))
84 | ).either
85 | .map(_.merge)
86 | rts.unsafeRunToFuture(zio)
87 | }
88 | }
89 |
90 | @accessible
91 | trait PlayLogger {
92 | def info(message: => String)(implicit mc: MarkerContext): UIO[Unit]
93 |
94 | def debug(message: => String)(implicit mc: MarkerContext): UIO[Unit]
95 | }
96 |
97 | object PlayLogger {
98 | def make(logger: Logger) =
99 | new PlayLogger {
100 | def info(message: => String)(implicit mc: MarkerContext) = UIO(logger.info(message))
101 |
102 | def debug(message: => String)(implicit mc: MarkerContext) = UIO(logger.debug(message))
103 | }
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/macros/src/main/scala/zio/macros/AccessibleMacro.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2020 John A. De Goes and the ZIO Contributors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package zio.macros
18 |
19 | import scala.reflect.macros.whitebox
20 |
21 | import com.github.ghik.silencer.silent
22 |
23 | /**
24 | * Generates method accessors for a service into annotated object.
25 | */
26 | private[macros] class AccessibleMacro(val c: whitebox.Context) {
27 | import c.universe._
28 |
29 | sealed trait Info {
30 | def service: ClassDef
31 |
32 | def serviceTypeParams: List[TypeDef]
33 | }
34 |
35 | protected case class PlainModuleInfo(
36 | module: Option[ModuleDef],
37 | service: ClassDef,
38 | serviceTypeParams: List[TypeDef]
39 | ) extends Info
40 |
41 | protected case class ModuleInfo(
42 | module: ModuleDef,
43 | service: ClassDef,
44 | serviceTypeParams: List[TypeDef]
45 | ) extends Info
46 |
47 | def abort(msg: String): Nothing = c.abort(c.enclosingPosition, msg)
48 |
49 | @silent("pattern var [^\\s]+ in method unapply is never used")
50 | def apply(annottees: c.Tree*): c.Tree = {
51 |
52 | val any: Tree = tq"_root_.scala.Any"
53 | val throwable: Tree = tq"_root_.java.lang.Throwable"
54 |
55 | val moduleInfo = (annottees match {
56 | case (module: ModuleDef) :: Nil =>
57 | module.impl.body.collectFirst {
58 | case service @ ClassDef(_, name, tparams, _) if name.toTermName.toString == "Service" =>
59 | ModuleInfo(module, service, tparams)
60 | }
61 | case (module: ModuleDef) :: (service @ ClassDef(_, _, tparams, _)) :: Nil =>
62 | Some(PlainModuleInfo(Some(module), service, tparams))
63 | case (service @ ClassDef(_, _, tparams, _)) :: (module: ModuleDef) :: Nil =>
64 | Some(PlainModuleInfo(Some(module), service, tparams))
65 | case (service @ ClassDef(_, _, tparams, _)) :: Nil =>
66 | Some(PlainModuleInfo(None, service, tparams))
67 | case _ => None
68 | }).getOrElse(abort("@accessible macro can only be applied to objects containing `Service` trait."))
69 |
70 | sealed trait Capability
71 | object Capability {
72 | case class Effect(r: Tree, e: Tree, a: Tree) extends Capability
73 | case class Managed(r: Tree, e: Tree, a: Tree) extends Capability
74 | case class Method(a: Tree) extends Capability
75 | case class Sink(r: Tree, e: Tree, a: Tree, l: Tree, b: Tree) extends Capability
76 | case class Stream(r: Tree, e: Tree, a: Tree) extends Capability
77 | }
78 |
79 | case class TypeInfo(capability: Capability) {
80 |
81 | val r: Tree = capability match {
82 | case Capability.Effect(r, _, _) => r
83 | case Capability.Managed(r, _, _) => r
84 | case Capability.Sink(r, _, _, _, _) => r
85 | case Capability.Stream(r, _, _) => r
86 | case Capability.Method(_) => any
87 | }
88 |
89 | val e: Tree = capability match {
90 | case Capability.Effect(_, e, _) => e
91 | case Capability.Managed(_, e, _) => e
92 | case Capability.Sink(_, e, _, _, _) => e
93 | case Capability.Stream(_, e, _) => e
94 | case Capability.Method(_) => throwable
95 | }
96 |
97 | val a: Tree = capability match {
98 | case Capability.Effect(_, _, a) => a
99 | case Capability.Managed(_, _, a) => a
100 | case Capability.Sink(_, e, a, l, b) => tq"_root_.zio.stream.ZSink[$any, $e, $a, $l, $b]"
101 | case Capability.Stream(_, e, a) => tq"_root_.zio.stream.ZStream[$any, $e, $a]"
102 | case Capability.Method(a) => a
103 | }
104 | }
105 |
106 | def typeInfo(tree: Tree): TypeInfo =
107 | tree match {
108 | case tq"$typeName[..$typeParams]" =>
109 | val typeArgs = typeParams.map(t => c.typecheck(tq"$t", c.TYPEmode, silent = true).tpe)
110 | val tpe = c.typecheck(tq"$typeName[..$typeArgs]", c.TYPEmode).tpe
111 | val dealiased = tpe.dealias
112 | val replacements: List[Tree] = (tpe.typeArgs zip typeParams).collect { case (NoType, t) =>
113 | q"$t"
114 | }
115 |
116 | val (typeArgTrees, _) = dealiased.typeArgs.foldLeft(List.empty[Tree] -> replacements) {
117 | case ((acc, x :: xs), NoType) => (acc :+ x) -> xs
118 | case ((acc, xs), t) => (acc :+ q"$t") -> xs
119 | }
120 |
121 | (dealiased.typeSymbol.fullName, typeArgTrees) match {
122 | case ("zio.ZIO", r :: e :: a :: Nil) => TypeInfo(Capability.Effect(r, e, a))
123 | case ("zio.ZManaged", r :: e :: a :: Nil) => TypeInfo(Capability.Managed(r, e, a))
124 | case ("zio.stream.ZSink", r :: e :: a :: l :: b :: Nil) => TypeInfo(Capability.Sink(r, e, a, l, b))
125 | case ("zio.stream.ZStream", r :: e :: a :: Nil) => TypeInfo(Capability.Stream(r, e, a))
126 | case _ => TypeInfo(Capability.Method(tree))
127 | }
128 | }
129 |
130 | def makeAccessor(
131 | name: TermName,
132 | info: TypeInfo,
133 | serviceTypeParams: List[TypeDef],
134 | typeParams: List[TypeDef],
135 | paramLists: List[List[ValDef]],
136 | isVal: Boolean,
137 | serviceName: TypeName
138 | ): Tree = {
139 |
140 | val serviceTypeArgs = serviceTypeParams.map(_.name)
141 |
142 | val returnType = info.capability match {
143 | case Capability.Effect(r, e, a) =>
144 | if (r != any) tq"_root_.zio.ZIO[_root_.zio.Has[$serviceName[..$serviceTypeArgs]] with $r, $e, $a]"
145 | else tq"_root_.zio.ZIO[_root_.zio.Has[$serviceName[..$serviceTypeArgs]], $e, $a]"
146 | case Capability.Managed(r, e, a) =>
147 | if (r != any) tq"_root_.zio.ZManaged[_root_.zio.Has[$serviceName[..$serviceTypeArgs]] with $r, $e, $a]"
148 | else tq"_root_.zio.ZManaged[_root_.zio.Has[$serviceName[..$serviceTypeArgs]], $e, $a]"
149 | case Capability.Stream(r, e, a) =>
150 | if (r != any) tq"_root_.zio.stream.ZStream[_root_.zio.Has[$serviceName[..$serviceTypeArgs]] with $r, $e, $a]"
151 | else tq"_root_.zio.stream.ZStream[_root_.zio.Has[$serviceName[..$serviceTypeArgs]], $e, $a]"
152 | case Capability.Sink(r, e, a, l, b) =>
153 | if (r != any) tq"_root_.zio.stream.ZSink[_root_.zio.Has[$serviceName[..$serviceTypeArgs]] with $r, $e, $a, $l, $b]"
154 | else tq"_root_.zio.stream.ZSink[_root_.zio.Has[$serviceName[..$serviceTypeArgs]], $e, $a, $l, $b]"
155 | case Capability.Method(a) =>
156 | tq"_root_.zio.ZIO[_root_.zio.Has[$serviceName[..$serviceTypeArgs]], $throwable, $a]"
157 | }
158 |
159 | val typeArgs = typeParams.map(_.name)
160 |
161 | def isRepeatedParamType(vd: ValDef) = vd.tpt match {
162 | case AppliedTypeTree(Select(_, nme), _) if nme == definitions.RepeatedParamClass.name => true
163 | case _ => false
164 | }
165 |
166 | val argNames = paramLists.map(_.map { arg =>
167 | if (isRepeatedParamType(arg)) q"${arg.name}: _*"
168 | else q"${arg.name}"
169 | })
170 |
171 | val returnValue = (info.capability, paramLists) match {
172 | case (_: Capability.Effect, argLists) if argLists.flatten.nonEmpty =>
173 | q"_root_.zio.ZIO.accessM(_.get[$serviceName[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))"
174 | case (_: Capability.Effect, _) =>
175 | q"_root_.zio.ZIO.accessM(_.get[$serviceName[..$serviceTypeArgs]].$name)"
176 | case (_: Capability.Managed, argLists) if argLists.flatten.nonEmpty =>
177 | q"_root_.zio.ZManaged.accessManaged(_.get[$serviceName[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))"
178 | case (_: Capability.Managed, _) =>
179 | q"_root_.zio.ZManaged.accessManaged(_.get[$serviceName[..$serviceTypeArgs]].$name[..$typeArgs])"
180 | case (_: Capability.Stream, argLists) if argLists.flatten.nonEmpty =>
181 | q"_root_.zio.stream.ZStream.accessStream(_.get[$serviceName[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))"
182 | case (_: Capability.Stream, _) =>
183 | q"_root_.zio.stream.ZStream.accessStream(_.get[$serviceName[..$serviceTypeArgs]].$name)"
184 | case (_: Capability.Sink, argLists) if argLists.flatten.nonEmpty =>
185 | q"_root_.zio.stream.ZSink.accessSink(_.get[$serviceName[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))"
186 | case (_: Capability.Sink, _) =>
187 | q"_root_.zio.stream.ZSink.accessSink(_.get[$serviceName[..$serviceTypeArgs]].$name)"
188 | case (_, argLists) if argLists.flatten.nonEmpty =>
189 | val argNames = argLists.map(_.map(_.name))
190 | q"_root_.zio.ZIO.access(_.get[$serviceName[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))"
191 | case (_, _) =>
192 | q"_root_.zio.ZIO.access(_.get[$serviceName[..$serviceTypeArgs]].$name)"
193 | }
194 |
195 | if (isVal && serviceTypeParams.isEmpty) q"val $name: $returnType = $returnValue"
196 | else {
197 | val allTypeParams =
198 | serviceTypeParams.map(tp => TypeDef(Modifiers(Flag.PARAM), tp.name, tp.tparams, tp.rhs)) ::: typeParams
199 | paramLists match {
200 | case Nil =>
201 | q"def $name[..$allTypeParams](implicit ev: _root_.izumi.reflect.Tag[$serviceName[..$serviceTypeArgs]]): $returnType = $returnValue"
202 | case List(Nil) =>
203 | q"def $name[..$allTypeParams]()(implicit ev: _root_.izumi.reflect.Tag[$serviceName[..$serviceTypeArgs]]): $returnType = $returnValue"
204 | case _ =>
205 | q"def $name[..$allTypeParams](...$paramLists)(implicit ev: _root_.izumi.reflect.Tag[$serviceName[..$serviceTypeArgs]]): $returnType = $returnValue"
206 | }
207 | }
208 | }
209 |
210 | val accessors =
211 | moduleInfo.service.impl.body.collect {
212 | case DefDef(_, termName, tparams, argLists, tree: Tree, _) if termName != TermName("$init$") =>
213 | makeAccessor(termName, typeInfo(tree), moduleInfo.serviceTypeParams, tparams, argLists, isVal = false, moduleInfo.service.name)
214 |
215 | case ValDef(_, termName, tree: Tree, _) =>
216 | makeAccessor(termName, typeInfo(tree), moduleInfo.serviceTypeParams, Nil, Nil, isVal = true, moduleInfo.service.name)
217 | }
218 |
219 | moduleInfo match {
220 | case ModuleInfo(q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }", _, _) =>
221 | q"""
222 | $mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
223 | ..$body
224 | ..$accessors
225 | }
226 | """
227 | case PlainModuleInfo(Some(q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }"), service, _) =>
228 | q"""
229 | ${service}
230 | $mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
231 | ..$body
232 | ..$accessors
233 | }
234 | """
235 | case PlainModuleInfo(None, service, _) =>
236 | q"""
237 | ${service}
238 | object ${service.name.toTermName} {
239 | ..$accessors
240 | }
241 | """
242 | case _ => abort("@accessible macro failure - could not unquote annotated object.")
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/macros/src/test/scala/zio/macros/AccessibleSpecPlain.scala:
--------------------------------------------------------------------------------
1 | package zio.macros
2 |
3 | import zio._
4 | import zio.stream._
5 | import zio.test.Assertion._
6 | import zio.test._
7 |
8 | object AccessibleSpecPlain extends DefaultRunnableSpec {
9 |
10 | def spec: ZSpec[Environment, Failure] = suite("AccessibleSpecPlain")(
11 | suite("Accessible macro")(
12 | testM("compiles when applied to object with empty Service") {
13 | assertM(typeCheck {
14 | """
15 | @accessible
16 | trait Module
17 | """
18 | })(isRight(anything))
19 | },
20 | testM("fails when applied to object without a Service") {
21 | assertM(typeCheck {
22 | """
23 | @accessible
24 | object Module
25 | """
26 | })(isLeft(anything))
27 | },
28 | testM("fails when applied to trait") {
29 | assertM(typeCheck {
30 | """
31 | @accessible
32 | trait Module
33 | """
34 | })(isRight(anything))
35 | },
36 | testM("fails when applied to class") {
37 | assertM(typeCheck {
38 | """
39 | @accessible
40 | class Module
41 | """
42 | })(isLeft(anything))
43 | },
44 | testM("generates accessor for values") {
45 | assertM(typeCheck {
46 | """
47 | @accessible
48 | trait Module {
49 | val foo: UIO[Unit]
50 | }
51 |
52 | object Check {
53 | val foo: ZIO[Has[Module], Nothing, Unit] =
54 | Module.foo
55 | }
56 | """
57 | })(isRight(anything))
58 | },
59 | testM("generates accessor for functions") {
60 | assertM(typeCheck {
61 | """
62 | @accessible
63 | trait Module {
64 | def foo(i: Int): UIO[Unit]
65 | }
66 |
67 | object Check {
68 | def foo(i: Int): ZIO[Has[Module], Nothing, Unit] =
69 | Module.foo(i)
70 | }
71 | """
72 | })(isRight(anything))
73 | },
74 | testM("generates accessor for varargs functions") {
75 | assertM(typeCheck {
76 | """
77 | @accessible
78 | trait Module {
79 | def varargsFoo(a: Int, b: Int*): UIO[Unit]
80 | }
81 |
82 | object Check {
83 | def varargsFoo(a: Int, b: Int*): ZIO[Has[Module], Nothing, Unit] =
84 | Module.varargsFoo(a, b: _*)
85 | }
86 | """
87 | })(isRight(anything))
88 | },
89 | testM("generates accessors for members returning ZManaged") {
90 | assertM(typeCheck {
91 | """
92 | @accessible
93 | trait Module {
94 | def umanaged(s: String): UManaged[Int]
95 | def urmanaged(s: String): URManaged[Has[String], Int]
96 | def zmanaged(s: String): ZManaged[Has[String], String, Int]
97 | }
98 |
99 | object Check {
100 | def umanaged(s: String): ZManaged[Has[Module], Nothing, Int] =
101 | Module.umanaged(s)
102 | def urmanaged(s: String): ZManaged[Has[String] with Has[Module], Nothing, Int] =
103 | Module.urmanaged(s)
104 | def zmanaged(s: String): ZManaged[Has[String] with Has[Module], String, Int] =
105 | Module.zmanaged(s)
106 | }
107 | """
108 | })(isRight(anything))
109 | },
110 | testM("generates accessor for service with default method implementations") {
111 | assertM(typeCheck {
112 | """
113 | @accessible
114 | trait Module {
115 | def foo(x: Int): Task[Unit] = foo(x.toString)
116 | def foo(x: String): Task[Unit]
117 | }
118 | """.stripMargin
119 | })(isRight(anything))
120 | },
121 | testM("generates accessor for service with one type param") {
122 | assertM(typeCheck {
123 | """
124 | @accessible
125 | trait Module[T] {
126 | val v: Task[T]
127 | def f1: UIO[Unit]
128 | def f2(): UIO[Unit]
129 | def f3(t: T): UIO[Unit]
130 | def f4(t: T)(i: Int): UIO[Unit]
131 | def f5(t: T)(implicit i: Int): UIO[Unit]
132 | def f6(t: T*): UIO[Unit]
133 | }
134 |
135 | object Check {
136 | def v[T: Tag]: ZIO[Has[Module[T]], Throwable, T] =
137 | Module.v[T]
138 | def f1[T: Tag]: ZIO[Has[Module[T]], Nothing, Unit] =
139 | Module.f1[T]
140 | def f2[T: Tag](): ZIO[Has[Module[T]], Nothing, Unit] =
141 | Module.f2[T]()
142 | def f3[T: Tag](t: T): ZIO[Has[Module[T]], Nothing, Unit] =
143 | Module.f3[T](t)
144 | def f4[T: Tag](t: T)(i: Int): ZIO[Has[Module[T]], Nothing, Unit] =
145 | Module.f4[T](t)(i)
146 | def f5[T: Tag](t: T)(implicit i: Int): ZIO[Has[Module[T]], Nothing, Unit] =
147 | Module.f5[T](t)
148 | def f6[T: Tag](t: T*): ZIO[Has[Module[T]], Nothing, Unit] =
149 | Module.f6[T](t: _*)
150 | }
151 | """
152 | })(isRight(anything))
153 | },
154 | testM("generates accessor for service with contravariant type param") {
155 | assertM(typeCheck {
156 | """
157 | @accessible
158 | trait Module[-R] {
159 | val v: RIO[R, Unit]
160 | }
161 |
162 | object Check {
163 | def v[R: Tag]: ZIO[Has[Module[R]] with R, Throwable, Unit] =
164 | Module.v[R]
165 | }
166 | """
167 | })(isRight(anything))
168 | },
169 | testM("generates accessor for service with two type params and type bounds") {
170 | assertM(typeCheck {
171 | """
172 | trait Foo
173 | trait Bar
174 |
175 | @accessible
176 | trait Module [T <: Foo, U >: Bar] {
177 | val v: Task[T]
178 | def f1: UIO[U]
179 | def f2(): UIO[U]
180 | def f3(t: T): UIO[U]
181 | def f4(t: T)(u: U): UIO[U]
182 | def f5(t: T)(implicit u: U): UIO[U]
183 | def f6(t: T*): UIO[U]
184 | }
185 |
186 | object Check {
187 | def v[T <: Foo: Tag, U >: Bar: Tag]: ZIO[Has[Module[T, U]], Throwable, T] =
188 | Module.v[T, U]
189 | def f1[T <: Foo: Tag, U >: Bar: Tag]: ZIO[Has[Module[T, U]], Nothing, U] =
190 | Module.f1[T, U]
191 | def f2[T <: Foo: Tag, U >: Bar: Tag](): ZIO[Has[Module[T, U]], Nothing, U] =
192 | Module.f2[T, U]()
193 | def f3[T <: Foo: Tag, U >: Bar: Tag](t: T): ZIO[Has[Module[T, U]], Nothing, U] =
194 | Module.f3[T, U](t)
195 | def f4[T <: Foo: Tag, U >: Bar: Tag](t: T)(u: U): ZIO[Has[Module[T, U]], Nothing, U] =
196 | Module.f4[T, U](t)(u)
197 | def f5[T <: Foo: Tag, U >: Bar: Tag](t: T)(implicit u: U): ZIO[Has[Module[T, U]], Nothing, U] =
198 | Module.f5[T, U](t)
199 | def f6[T <: Foo: Tag, U >: Bar: Tag](t: T*): ZIO[Has[Module[T, U]], Nothing, U] =
200 | Module.f6[T, U](t: _*)
201 | }
202 | """
203 | })(isRight(anything))
204 | },
205 | testM("generates accessors for all capabilities") {
206 | assertM(typeCheck {
207 | """
208 | @accessible
209 | trait Module {
210 | val static : UIO[String]
211 | def zeroArgs : UIO[Int]
212 | def zeroArgsWithParens() : UIO[Long]
213 | def singleArg(arg1: Int) : UIO[String]
214 | def multiArgs(arg1: Int, arg2: Long) : UIO[String]
215 | def multiParamLists(arg1: Int)(arg2: Long) : UIO[String]
216 | def typedVarargs[T](arg1: Int, arg2: T*) : UIO[T]
217 | def command(arg1: Int) : UIO[Unit]
218 | def overloaded(arg1: Int) : UIO[String]
219 | def overloaded(arg1: Long) : UIO[String]
220 |
221 | val staticManaged : UManaged[String]
222 | def zeroArgsManaged : UManaged[Int]
223 | def zeroArgsTypedManaged[T] : UManaged[T]
224 | def zeroArgsWithParensManaged() : UManaged[Long]
225 | def singleArgManaged(arg1: Int) : UManaged[String]
226 | def multiArgsManaged(arg1: Int, arg2: Long) : UManaged[String]
227 | def multiParamListsManaged(arg1: Int)(arg2: Long) : UManaged[String]
228 | def typedVarargsManaged[T](arg1: Int, arg2: T*) : UManaged[T]
229 | def commandManaged(arg1: Int) : UManaged[Unit]
230 | def overloadedManaged(arg1: Int) : UManaged[String]
231 | def overloadedManaged(arg1: Long) : UManaged[String]
232 |
233 | def function(arg1: Int) : String
234 | def sink(arg1: Int) : ZSink[Any, Nothing, Int, Int, List[Int]]
235 | def stream(arg1: Int) : ZStream[Any, Nothing, Int]
236 | }
237 |
238 | object Check {
239 | val static : ZIO[Has[Module], Nothing, String] = Module.static
240 | def zeroArgs : ZIO[Has[Module], Nothing, Int] = Module.zeroArgs
241 | def zeroArgsWithParens() : ZIO[Has[Module], Nothing, Long] = Module.zeroArgsWithParens()
242 | def singleArg(arg1: Int) : ZIO[Has[Module], Nothing, String] = Module.singleArg(arg1)
243 | def multiArgs(arg1: Int, arg2: Long) : ZIO[Has[Module], Nothing, String] = Module.multiArgs(arg1, arg2)
244 | def multiParamLists(arg1: Int)(arg2: Long) : ZIO[Has[Module], Nothing, String] = Module.multiParamLists(arg1)(arg2)
245 | def typedVarargs[T](arg1: Int, arg2: T*) : ZIO[Has[Module], Nothing, T] = Module.typedVarargs[T](arg1, arg2: _*)
246 | def command(arg1: Int) : ZIO[Has[Module], Nothing, Unit] = Module.command(arg1)
247 | def overloaded(arg1: Int) : ZIO[Has[Module], Nothing, String] = Module.overloaded(arg1)
248 | def overloaded(arg1: Long) : ZIO[Has[Module], Nothing, String] = Module.overloaded(arg1)
249 |
250 | val staticManaged : ZManaged[Has[Module], Nothing, String] = Module.staticManaged
251 | def zeroArgsManaged : ZManaged[Has[Module], Nothing, Int] = Module.zeroArgsManaged
252 | def zeroArgsTypedManaged[T] : ZManaged[Has[Module], Nothing, T] = Module.zeroArgsTypedManaged[T]
253 | def zeroArgsWithParensManaged() : ZManaged[Has[Module], Nothing, Long] = Module.zeroArgsWithParensManaged()
254 | def singleArgManaged(arg1: Int) : ZManaged[Has[Module], Nothing, String] = Module.singleArgManaged(arg1)
255 | def multiArgsManaged(arg1: Int, arg2: Long) : ZManaged[Has[Module], Nothing, String] = Module.multiArgsManaged(arg1, arg2)
256 | def multiParamListsManaged(arg1: Int)(arg2: Long) : ZManaged[Has[Module], Nothing, String] = Module.multiParamListsManaged(arg1)(arg2)
257 | def typedVarargsManaged[T](arg1: Int, arg2: T*) : ZManaged[Has[Module], Nothing, T] = Module.typedVarargsManaged[T](arg1, arg2: _*)
258 | def commandManaged(arg1: Int) : ZManaged[Has[Module], Nothing, Unit] = Module.commandManaged(arg1)
259 | def overloadedManaged(arg1: Int) : ZManaged[Has[Module], Nothing, String] = Module.overloadedManaged(arg1)
260 | def overloadedManaged(arg1: Long) : ZManaged[Has[Module], Nothing, String] = Module.overloadedManaged(arg1)
261 |
262 | def function(arg1: Int) : ZIO[Has[Module], Throwable, String] = Module.function(arg1)
263 | def sink(arg1: Int) : ZSink[Has[Module], Nothing, Int, Int, List[Int]] = Module.sink(arg1)
264 | def stream(arg1: Int) : ZStream[Has[Module], Nothing, Int] = Module.stream(arg1)
265 | }
266 | """
267 | })(isRight(anything))
268 | }
269 | )
270 | )
271 | }
272 |
--------------------------------------------------------------------------------
/macros/src/test/scala/zio/macros/AccessibleSpec.scala:
--------------------------------------------------------------------------------
1 | package zio.macros
2 |
3 | import zio._
4 | import zio.stream._
5 | import zio.test.Assertion._
6 | import zio.test._
7 |
8 | object AccessibleSpec extends DefaultRunnableSpec {
9 |
10 | def spec: ZSpec[Environment, Failure] = suite("AccessibleSpec")(
11 | suite("Accessible macro")(
12 | testM("compiles when applied to object with empty Service") {
13 | assertM(typeCheck {
14 | """
15 | @accessible
16 | object Module {
17 | trait Service
18 | }
19 | """
20 | })(isRight(anything))
21 | },
22 | testM("fails when applied to object without a Service") {
23 | assertM(typeCheck {
24 | """
25 | @accessible
26 | object Module
27 | """
28 | })(isLeft(anything))
29 | },
30 | testM("fails when applied to trait") {
31 | assertM(typeCheck {
32 | """
33 | @accessible
34 | trait Module
35 | """
36 | })(isRight(anything))
37 | },
38 | testM("fails when applied to class") {
39 | assertM(typeCheck {
40 | """
41 | @accessible
42 | class Module
43 | """
44 | })(isLeft(anything))
45 | },
46 | testM("generates accessor for values") {
47 | assertM(typeCheck {
48 | """
49 | @accessible
50 | object Module {
51 | trait Service {
52 | val foo: UIO[Unit]
53 | }
54 | }
55 |
56 | object Check {
57 | val foo: ZIO[Has[Module.Service], Nothing, Unit] =
58 | Module.foo
59 | }
60 | """
61 | })(isRight(anything))
62 | },
63 | testM("generates accessor for functions") {
64 | assertM(typeCheck {
65 | """
66 | @accessible
67 | object Module {
68 | trait Service {
69 | def foo(i: Int): UIO[Unit]
70 | }
71 | }
72 |
73 | object Check {
74 | def foo(i: Int): ZIO[Has[Module.Service], Nothing, Unit] =
75 | Module.foo(i)
76 | }
77 | """
78 | })(isRight(anything))
79 | },
80 | testM("generates accessor for varargs functions") {
81 | assertM(typeCheck {
82 | """
83 | @accessible
84 | object Module {
85 | trait Service {
86 | def varargsFoo(a: Int, b: Int*): UIO[Unit]
87 | }
88 | }
89 |
90 | object Check {
91 | def varargsFoo(a: Int, b: Int*): ZIO[Has[Module.Service], Nothing, Unit] =
92 | Module.varargsFoo(a, b: _*)
93 | }
94 | """
95 | })(isRight(anything))
96 | },
97 | testM("generates accessors for members returning ZManaged") {
98 | assertM(typeCheck {
99 | """
100 | @accessible
101 | object Module {
102 | trait Service {
103 | def umanaged(s: String): UManaged[Int]
104 | def urmanaged(s: String): URManaged[Has[String], Int]
105 | def zmanaged(s: String): ZManaged[Has[String], String, Int]
106 | }
107 | }
108 |
109 | object Check {
110 | def umanaged(s: String): ZManaged[Has[Module.Service], Nothing, Int] =
111 | Module.umanaged(s)
112 | def urmanaged(s: String): ZManaged[Has[String] with Has[Module.Service], Nothing, Int] =
113 | Module.urmanaged(s)
114 | def zmanaged(s: String): ZManaged[Has[String] with Has[Module.Service], String, Int] =
115 | Module.zmanaged(s)
116 | }
117 | """
118 | })(isRight(anything))
119 | },
120 | testM("generates accessor for service with default method implementations") {
121 | assertM(typeCheck {
122 | """
123 | @accessible
124 | object Module {
125 | trait Service {
126 | def foo(x: Int): Task[Unit] = foo(x.toString)
127 | def foo(x: String): Task[Unit]
128 | }
129 | }
130 | """.stripMargin
131 | })(isRight(anything))
132 | },
133 | testM("generates accessor for service with one type param") {
134 | assertM(typeCheck {
135 | """
136 | @accessible
137 | object Module {
138 | trait Service[T] {
139 | val v: Task[T]
140 | def f1: UIO[Unit]
141 | def f2(): UIO[Unit]
142 | def f3(t: T): UIO[Unit]
143 | def f4(t: T)(i: Int): UIO[Unit]
144 | def f5(t: T)(implicit i: Int): UIO[Unit]
145 | def f6(t: T*): UIO[Unit]
146 | }
147 | }
148 |
149 | object Check {
150 | def v[T: Tag]: ZIO[Has[Module.Service[T]], Throwable, T] =
151 | Module.v[T]
152 | def f1[T: Tag]: ZIO[Has[Module.Service[T]], Nothing, Unit] =
153 | Module.f1[T]
154 | def f2[T: Tag](): ZIO[Has[Module.Service[T]], Nothing, Unit] =
155 | Module.f2[T]()
156 | def f3[T: Tag](t: T): ZIO[Has[Module.Service[T]], Nothing, Unit] =
157 | Module.f3[T](t)
158 | def f4[T: Tag](t: T)(i: Int): ZIO[Has[Module.Service[T]], Nothing, Unit] =
159 | Module.f4[T](t)(i)
160 | def f5[T: Tag](t: T)(implicit i: Int): ZIO[Has[Module.Service[T]], Nothing, Unit] =
161 | Module.f5[T](t)
162 | def f6[T: Tag](t: T*): ZIO[Has[Module.Service[T]], Nothing, Unit] =
163 | Module.f6[T](t: _*)
164 | }
165 | """
166 | })(isRight(anything))
167 | },
168 | testM("generates accessor for service with contravariant type param") {
169 | assertM(typeCheck {
170 | """
171 | @accessible
172 | object Module {
173 | trait Service[-R] {
174 | val v: RIO[R, Unit]
175 | }
176 | }
177 |
178 | object Check {
179 | def v[R: Tag]: ZIO[Has[Module.Service[R]] with R, Throwable, Unit] =
180 | Module.v[R]
181 | }
182 | """
183 | })(isRight(anything))
184 | },
185 | testM("generates accessor for service with two type params and type bounds") {
186 | assertM(typeCheck {
187 | """
188 | trait Foo
189 | trait Bar
190 |
191 | @accessible
192 | object Module {
193 | trait Service[T <: Foo, U >: Bar] {
194 | val v: Task[T]
195 | def f1: UIO[U]
196 | def f2(): UIO[U]
197 | def f3(t: T): UIO[U]
198 | def f4(t: T)(u: U): UIO[U]
199 | def f5(t: T)(implicit u: U): UIO[U]
200 | def f6(t: T*): UIO[U]
201 | }
202 | }
203 |
204 | object Check {
205 | def v[T <: Foo: Tag, U >: Bar: Tag]: ZIO[Has[Module.Service[T, U]], Throwable, T] =
206 | Module.v[T, U]
207 | def f1[T <: Foo: Tag, U >: Bar: Tag]: ZIO[Has[Module.Service[T, U]], Nothing, U] =
208 | Module.f1[T, U]
209 | def f2[T <: Foo: Tag, U >: Bar: Tag](): ZIO[Has[Module.Service[T, U]], Nothing, U] =
210 | Module.f2[T, U]()
211 | def f3[T <: Foo: Tag, U >: Bar: Tag](t: T): ZIO[Has[Module.Service[T, U]], Nothing, U] =
212 | Module.f3[T, U](t)
213 | def f4[T <: Foo: Tag, U >: Bar: Tag](t: T)(u: U): ZIO[Has[Module.Service[T, U]], Nothing, U] =
214 | Module.f4[T, U](t)(u)
215 | def f5[T <: Foo: Tag, U >: Bar: Tag](t: T)(implicit u: U): ZIO[Has[Module.Service[T, U]], Nothing, U] =
216 | Module.f5[T, U](t)
217 | def f6[T <: Foo: Tag, U >: Bar: Tag](t: T*): ZIO[Has[Module.Service[T, U]], Nothing, U] =
218 | Module.f6[T, U](t: _*)
219 | }
220 | """
221 | })(isRight(anything))
222 | },
223 | testM("generates accessors for all capabilities") {
224 | assertM(typeCheck {
225 | """
226 | @accessible
227 | object Module {
228 | trait Service {
229 | val static : UIO[String]
230 | def zeroArgs : UIO[Int]
231 | def zeroArgsWithParens() : UIO[Long]
232 | def singleArg(arg1: Int) : UIO[String]
233 | def multiArgs(arg1: Int, arg2: Long) : UIO[String]
234 | def multiParamLists(arg1: Int)(arg2: Long) : UIO[String]
235 | def typedVarargs[T](arg1: Int, arg2: T*) : UIO[T]
236 | def command(arg1: Int) : UIO[Unit]
237 | def overloaded(arg1: Int) : UIO[String]
238 | def overloaded(arg1: Long) : UIO[String]
239 |
240 | val staticManaged : UManaged[String]
241 | def zeroArgsManaged : UManaged[Int]
242 | def zeroArgsTypedManaged[T] : UManaged[T]
243 | def zeroArgsWithParensManaged() : UManaged[Long]
244 | def singleArgManaged(arg1: Int) : UManaged[String]
245 | def multiArgsManaged(arg1: Int, arg2: Long) : UManaged[String]
246 | def multiParamListsManaged(arg1: Int)(arg2: Long) : UManaged[String]
247 | def typedVarargsManaged[T](arg1: Int, arg2: T*) : UManaged[T]
248 | def commandManaged(arg1: Int) : UManaged[Unit]
249 | def overloadedManaged(arg1: Int) : UManaged[String]
250 | def overloadedManaged(arg1: Long) : UManaged[String]
251 |
252 | def function(arg1: Int) : String
253 | def sink(arg1: Int) : ZSink[Any, Nothing, Int, Int, List[Int]]
254 | def stream(arg1: Int) : ZStream[Any, Nothing, Int]
255 | }
256 | }
257 |
258 | object Check {
259 | val static : ZIO[Has[Module.Service], Nothing, String] = Module.static
260 | def zeroArgs : ZIO[Has[Module.Service], Nothing, Int] = Module.zeroArgs
261 | def zeroArgsWithParens() : ZIO[Has[Module.Service], Nothing, Long] = Module.zeroArgsWithParens()
262 | def singleArg(arg1: Int) : ZIO[Has[Module.Service], Nothing, String] = Module.singleArg(arg1)
263 | def multiArgs(arg1: Int, arg2: Long) : ZIO[Has[Module.Service], Nothing, String] = Module.multiArgs(arg1, arg2)
264 | def multiParamLists(arg1: Int)(arg2: Long) : ZIO[Has[Module.Service], Nothing, String] = Module.multiParamLists(arg1)(arg2)
265 | def typedVarargs[T](arg1: Int, arg2: T*) : ZIO[Has[Module.Service], Nothing, T] = Module.typedVarargs[T](arg1, arg2: _*)
266 | def command(arg1: Int) : ZIO[Has[Module.Service], Nothing, Unit] = Module.command(arg1)
267 | def overloaded(arg1: Int) : ZIO[Has[Module.Service], Nothing, String] = Module.overloaded(arg1)
268 | def overloaded(arg1: Long) : ZIO[Has[Module.Service], Nothing, String] = Module.overloaded(arg1)
269 |
270 | val staticManaged : ZManaged[Has[Module.Service], Nothing, String] = Module.staticManaged
271 | def zeroArgsManaged : ZManaged[Has[Module.Service], Nothing, Int] = Module.zeroArgsManaged
272 | def zeroArgsTypedManaged[T] : ZManaged[Has[Module.Service], Nothing, T] = Module.zeroArgsTypedManaged[T]
273 | def zeroArgsWithParensManaged() : ZManaged[Has[Module.Service], Nothing, Long] = Module.zeroArgsWithParensManaged()
274 | def singleArgManaged(arg1: Int) : ZManaged[Has[Module.Service], Nothing, String] = Module.singleArgManaged(arg1)
275 | def multiArgsManaged(arg1: Int, arg2: Long) : ZManaged[Has[Module.Service], Nothing, String] = Module.multiArgsManaged(arg1, arg2)
276 | def multiParamListsManaged(arg1: Int)(arg2: Long) : ZManaged[Has[Module.Service], Nothing, String] = Module.multiParamListsManaged(arg1)(arg2)
277 | def typedVarargsManaged[T](arg1: Int, arg2: T*) : ZManaged[Has[Module.Service], Nothing, T] = Module.typedVarargsManaged[T](arg1, arg2: _*)
278 | def commandManaged(arg1: Int) : ZManaged[Has[Module.Service], Nothing, Unit] = Module.commandManaged(arg1)
279 | def overloadedManaged(arg1: Int) : ZManaged[Has[Module.Service], Nothing, String] = Module.overloadedManaged(arg1)
280 | def overloadedManaged(arg1: Long) : ZManaged[Has[Module.Service], Nothing, String] = Module.overloadedManaged(arg1)
281 |
282 | def function(arg1: Int) : ZIO[Has[Module.Service], Throwable, String] = Module.function(arg1)
283 | def sink(arg1: Int) : ZSink[Has[Module.Service], Nothing, Int, Int, List[Int]] = Module.sink(arg1)
284 | def stream(arg1: Int) : ZStream[Has[Module.Service], Nothing, Int] = Module.stream(arg1)
285 | }
286 | """
287 | })(isRight(anything))
288 | }
289 | )
290 | )
291 | }
292 |
--------------------------------------------------------------------------------