├── .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 | 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 | --------------------------------------------------------------------------------