diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 0ad13e76a..1bf718981 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,31 @@ -project/boot +*.class +*.log + +# sbt specific +.cache +.history +.lib/ +dist/* target +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ + +# Scala-IDE specific +.scala_dependencies +.worksheet + +# ENSIME specific +.ensime_cache/ .ensime -.ensime_lucene -.ensime_cache -TAGS -\#*# -*~ -.#* -.lib -.history -.*.swp -.idea -.idea/* -.idea_modules + +# IDEA Specific +.idea/ +*.iws +/out/ +.idea_modules/ + .DS_Store -.sbtrc -*.sublime-project -*.sublime-workspace -tests.iml -*.log -metastore_db/ -checkpoint/ -testData/ -.phantom-temp + +/secring.gpg \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 000000000..10b7ee175 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,22 @@ +style = defaultWithAlign +maxColumn = 100 + +continuationIndent.callSite = 2 + +newlines { + sometimesBeforeColonInMethodReturnType = false +} + +align { + arrowEnumeratorGenerator = false + ifWhileOpenParen = false + openParenCallSite = false + openParenDefnSite = false +} + +docstrings = JavaDoc + +rewrite { + rules = [SortImports, RedundantBraces] + redundantBraces.maxLines = 1 +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index aca06c16b..4ff8f9145 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,30 +1,44 @@ language: scala -jdk: -- oraclejdk8 + scala: -- 2.12.1 -- 2.11.8 +- 2.11.11 +- 2.12.2 -#install: -#- pip install --user codecov +jdk: +- oraclejdk8 -script: -- sbt ++$TRAVIS_SCALA_VERSION validate -#- sbt ++$TRAVIS_SCALA_VERSION coverage validate coverageReport -#- codecov +before_cache: +- du -h -d 1 $HOME/.ivy2/ +- du -h -d 2 $HOME/.sbt/ +- du -h -d 4 $HOME/.coursier/ +- find $HOME/.sbt -name "*.lock" -type f -delete +- find $HOME/.ivy2/cache -name "ivydata-*.properties" -type f -delete cache: directories: + - $HOME/.sbt/cache - $HOME/.sbt/0.13 + - $HOME/.sbt/boot/ - $HOME/.sbt/boot/scala* - - $HOME/.sbt/cache - $HOME/.sbt/launchers + - $HOME/.ivy2/cache - $HOME/.ivy2 - - $HOME/.coursier -before_cache: -- du -h -d 1 $HOME/.ivy2/ -- du -h -d 2 $HOME/.sbt/ -- du -h -d 4 $HOME/.coursier/ -- find $HOME/.sbt -name "*.lock" -type f -delete -- find $HOME/.ivy2/cache -name "ivydata-*.properties" -type f -delete -- find $HOME/.coursier/cache -name "*.lock" -type f -delete + +before_install: +- if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then + openssl aes-256-cbc -K $encrypted_80bb47bfd841_key -iv $encrypted_80bb47bfd841_iv -in secring.gpg.enc -out secring.gpg -d; + fi +- export PATH=${PATH}:./vendor/bundle + +install: +- rvm use 2.2.3 --install --fuzzy +- gem update --system +- gem install sass +- gem install jekyll -v 3.4.3 + +script: +- sbt ++$TRAVIS_SCALA_VERSION orgScriptCI + +after_success: +- bash <(curl -s https://codecov.io/bash) -t token_replace-me +- sbt ++$TRAVIS_SCALA_VERSION orgAfterCISuccess \ No newline at end of file diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 000000000..4a99986eb --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,12 @@ +# Authors + +## Maintainers + +The maintainers of the project are: + +* 47 Degrees (twitter: @47deg) <[47degfreestyle](https://github.com/47degfreestyle)> + +## Contributors + +These are the people that have contributed to the freestyle-rpc project: + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..5ddad421e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..fecadae8a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing + +Discussion around Freestyle happens in the [Gitter channel](https://gitter.im/47deg/freestyle) as well as on +[GitHub issues](https://github.com/47deg/freestyle/issues) and [pull requests](https://github.com/47deg/freestyle/pulls). + +Feel free to open an issue if you notice a bug, have an idea for a feature, or have a question about +the code. Pull requests are also welcome. + +People are expected to follow the [Typelevel Code of Conduct](http://typelevel.org/conduct.html) when discussing Freestyle on the Github page, Gitter channel, or other venues. + +If you are being harassed, please contact one of [us](AUTHORS.md#maintainers) immediately so that we can support you. In case you cannot get in touch with us please write an email to [47 Degrees](mailto:hello@47deg.com). + +## How can I help? + +Freestyle follows a standard [fork and pull](https://help.github.com/articles/using-pull-requests/) model for contributions via GitHub pull requests. + +The process is simple: + + 1. Find something you want to work on + 2. Let us know you are working on it via the Gitter channel or GitHub issues/pull requests + 3. Implement your contribution + 4. Write tests + 5. Update the documentation + 6. Submit pull request + +You will be automatically included in the [AUTHORS.md](AUTHORS.md#contributors) file as contributor in the next release. +If you encounter any confusion or frustration during the contribution process, please create a GitHub issue and we'll do our best to improve the process. \ No newline at end of file diff --git a/COPYING b/COPYING deleted file mode 100644 index 21e67ba58..000000000 --- a/COPYING +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2017 Andy Scott - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..fbf62f7b8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (C) 2017 47 Degrees. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 000000000..99895ddeb --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,4 @@ +Freestyle +Copyright (c) 2017 47 Degrees. All rights reserved. + +Licensed under Apache License. See [LICENSE](LICENSE) for terms. \ No newline at end of file diff --git a/README.md b/README.md index f7fcfe54c..56626679e 100644 --- a/README.md +++ b/README.md @@ -1,138 +1,19 @@ -### Mezzo -[![Build Status](https://api.travis-ci.org/andyscott/mezzo.png?branch=master)](https://travis-ci.org/andyscott/mezzo) +[comment]: # (Start Badges) +[![Build Status](https://travis-ci.org/frees-io/freestyle-rpc.svg?branch=master)](https://travis-ci.org/frees-io/freestyle-rpc) [![codecov.io](http://codecov.io/github/frees-io/freestyle-rpc/coverage.svg?branch=master)](http://codecov.io/github/frees-io/freestyle-rpc?branch=master) [![Maven Central](https://img.shields.io/badge/maven%20central-0.0.1-green.svg)](https://oss.sonatype.org/#nexus-search;gav~io.frees~freestyle*) [![Latest version](https://img.shields.io/badge/freestyle--rpc-0.0.1-green.svg)](https://index.scala-lang.org/frees-io/freestyle-rpc) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/frees-io/freestyle-rpc/master/LICENSE) [![Join the chat at https://gitter.im/47deg/freestyle](https://badges.gitter.im/47deg/freestyle.svg)](https://gitter.im/47deg/freestyle?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GitHub Issues](https://img.shields.io/github/issues/frees-io/freestyle-rpc.svg)](https://github.com/frees-io/freestyle-rpc/issues) [![Scala.js](http://scala-js.org/assets/badges/scalajs-0.6.15.svg)](http://scala-js.org) +[comment]: # (End Badges) -### Introduction +# freestyle-rpc -Mezzo is a hydration framework for watering and growing your code from a -definition of your API. +RPC with Freestyle -What does this mean? +[comment]: # (Start Copyright) +# Copyright -Consider a simple API for a counter: +Freestyle is designed and developed by 47 Degrees -```scala -import scala.concurrent.Future +Copyright (C) 2017 47 Degrees. -trait CounterAPI { - def adjust(delta: Long): Future[Long] - def reset() : Future[Unit] - def read() : Future[Long] -} -``` - -This is a very traditional looking API. What we really want to -do is model our API as a series of case classes (an ADT). This will -allow to get a lot of work done for free. - -```scala -sealed abstract class CounterOp[A] extends Product with Serializable -object CounterOp { - case class Adjust(delta: Long) extends CounterOp[Long] - case object Reset extends CounterOp[Unit] - case object Read extends CounterOp[Long] -} -``` - -If we want, we can implement the traditional interface by lifting our ADT -operations into our return type of Future. We can do this with a natural -transformation (`~>`). - -```scala -import cats.~> - -case class CounterOps(eval: CounterOp ~> Future) extends CounterAPI { - import CounterOp._ - - def adjust(delta: Long): Future[Long] = eval(Adjust(delta)) - def reset() : Future[Unit] = eval(Reset) - def read() : Future[Long] = eval(Read) -} -``` - -We can go ahead and implement our API using a natural transformation. -This is a lot like writing the body of an Actor, except the body is -strongly typed. - -```scala -class DummyCounter extends (CounterOp ~> Future) { - - override def apply[A](rawOp: CounterOp[A]): Future[A] = rawOp match { - case op: CounterOp.Adjust => handleAdjust(op) - case CounterOp.Reset => handleReset() - case CounterOp.Read => handleRead() - } - - @volatile private[this] var count: Long = 0 - - private[this] def handleAdjust(op: CounterOp.Adjust): Future[Long] = - Future { count = count + op.delta; count } - private[this] def handleReset(): Future[Unit] = - Future { count = 0 } - private[this] def handleRead(): Future[Long] = - Future { count } -} -``` - -We can now instantly hydrate a HTTP server and HTTP client for our API. - -```scala -import mezzo.Hydrate -import mezzo.h2akka._ -import io.circe.generic.auto._ -``` - -*server:* -```scala -import akka.http.scaladsl.Http - -val backend = new DummyCounter() -val routes = Hydrate[AkkaHttpRoutes].hydrate[CounterOp].apply(backend) -val binding = Http().bindAndHandle(routes, "localhost", 8080) -``` - -*client:* -```scala -import akka.http.scaladsl.Http - -val handler = AkkaClientRequestHandler(system, "http://localhost:8080/") -val client = Hydrate[AkkaHttpClient].hydrate[CounterOp].apply(handler) -val counter = CounterOps(client) -``` - -And the client works as expected: - -```scala -Await.result(counter.read(), 10.seconds) -// res4: Long = 0 - -Await.result(counter.adjust(1), 10.seconds) -// res5: Long = 1 - -Await.result(counter.adjust(10), 10.seconds) -// res6: Long = 11 - -Await.result(counter.adjust(100), 10.seconds) -// res7: Long = 111 - -Await.result(counter.read(), 10.seconds) -// res8: Long = 111 - -Await.result(counter.reset(), 10.seconds) - -Await.result(counter.read(), 10.seconds) -// res10: Long = 0 -``` - - - -### Documentation - -Documetation coming soon. - -### License -The license can be found in [COPYING]. - -[COPYING]: COPYING +[comment]: # (End Copyright) \ No newline at end of file diff --git a/build.sbt b/build.sbt index d36e032bf..310a41e0d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,96 +1,27 @@ -import UnidocKeys._ - -lazy val V = new { - lazy val akkahttp = "10.0.3" - lazy val cats = "0.9.0" - lazy val circe = "0.7.0" - lazy val shapeless = "2.3.2" - lazy val scalacheck = "1.13.4" - lazy val scalacheckShapeless = "1.1.3" -} - -def module(modName: String): Project = - Project(modName, file(s"""modules/$modName""")) - .settings(name := s"$modName") - -addCommandAlias("validate", ";" + List( - "compile", - "readme/tut", "copyReadme", "checkDiff" -).mkString(";")) - lazy val root = (project in file(".")) .settings(noPublishSettings) - .aggregate(`core`) - .aggregate(`http-akka`) - .aggregate(`demo`) - .settings(TaskKey[Unit]("copyReadme") := { - (tutTargetDirectory in `readme`).value.listFiles().foreach(file => - IO.copyFile(file, new File((baseDirectory in ThisBuild).value, file.name))) - }) - .settings(TaskKey[Unit]("checkDiff") := { - val diff = "git diff".!! - if (diff.nonEmpty) sys.error("Working directory is dirty!\n" + diff) - }) - -lazy val `core` = module("core") - .settings(macroSettings) - .settings(crossVersionSharedSources) - .settings(libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % V.cats, - "com.chuusai" %% "shapeless" % V.shapeless - )) - -lazy val `http-akka` = module("http-akka") - .dependsOn(core) - .settings(libraryDependencies ++= Seq( - "com.typesafe.akka" %% "akka-http" % V.akkahttp, - "io.circe" %% "circe-core" % V.circe, - "io.circe" %% "circe-parser" % V.circe - )) + .aggregate(httpCore, httpAkka, httpDemo) -lazy val `demo` = module("demo") - .dependsOn(`http-akka`) - .settings(noPublishSettings) - .settings(libraryDependencies ++= Seq( - "io.circe" %% "circe-generic" % V.circe - )) +lazy val httpCore = (project in file("http/core")) + .settings( + libraryDependencies ++= Seq( + %%("cats-core"), + %%("shapeless") + )) -lazy val `readme` = module("readme") - .dependsOn(`core`) - .dependsOn(`http-akka`) +lazy val httpAkka = (project in file("http/akka")) + .dependsOn(httpCore) + .settings( + libraryDependencies ++= Seq( + %%("akka-http"), + %%("circe-core"), + %%("circe-parser") + )) + +lazy val httpDemo = (project in file("http/demo")) + .dependsOn(httpAkka) .settings(noPublishSettings) - .settings(tutSettings) - .settings(libraryDependencies ++= Seq( - "io.circe" %% "circe-generic" % V.circe - )) .settings( - tutScalacOptions ~= (_.filterNot(Set("-Yno-predef")))) - -lazy val macroSettings: Seq[Setting[_]] = Seq( - libraryDependencies ++= Seq( - scalaOrganization.value % "scala-compiler" % scalaVersion.value % Provided, - scalaOrganization.value % "scala-reflect" % scalaVersion.value % Provided, - "org.typelevel" %% "macro-compat" % "1.1.1", - compilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.patch) - ), - libraryDependencies ++= { - CrossVersion.partialVersion(scalaVersion.value) match { - // if scala 2.11+ is used, quasiquotes are merged into scala-reflect. - case Some((2, scalaMajor)) if scalaMajor >= 11 => Nil - // in Scala 2.10, quasiquotes are provided by macro paradise. - case Some((2, 10)) => Seq("org.scalamacros" %% "quasiquotes" % "2.1.0" cross CrossVersion.binary) - } - } -) - -lazy val crossVersionSharedSources: Seq[Setting[_]] = - Seq(Compile, Test).map { sc => - (unmanagedSourceDirectories in sc) ++= { - (unmanagedSourceDirectories in sc ).value.flatMap { dir: File => - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, y)) if y == 11 => Some(new File(dir.getPath + "_2.11")) - case _ => None - } - } - } - } + libraryDependencies ++= Seq( + %%("circe-generic") + )) diff --git a/modules/http-akka/src/main/scala/mezzo/h2akka/AkkaHttpClient.scala b/http/akka/src/main/scala/AkkaHttpClient.scala similarity index 57% rename from modules/http-akka/src/main/scala/mezzo/h2akka/AkkaHttpClient.scala rename to http/akka/src/main/scala/AkkaHttpClient.scala index b9f2c0170..55e96229f 100644 --- a/modules/http-akka/src/main/scala/mezzo/h2akka/AkkaHttpClient.scala +++ b/http/akka/src/main/scala/AkkaHttpClient.scala @@ -1,16 +1,27 @@ -/* - - * Mezzo [http-akka] +/* + * Copyright 2017 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ -package mezzo +package freestyle +package http package h2akka import io.circe.Decoder import io.circe.Encoder -import io.circe.parser.{ parse => parseJSON } - +import io.circe.parser.{parse => parseJSON} import cats._ - import akka.actor.ActorSystem import akka.stream.ActorMaterializer import akka.stream.Materializer @@ -24,29 +35,30 @@ import akka.http.scaladsl.model.HttpResponse import akka.http.scaladsl.model.HttpRequest import akka.http.scaladsl.model.RequestEntity import akka.http.scaladsl.unmarshalling.Unmarshal +import freestyle.http.core._ import scala.concurrent.ExecutionContext import scala.concurrent.Future private[h2akka] final class HydrateAkkaHttpClient[H <: HydrationMacros](h0: H) - extends Hydration(h0) -{ + extends Hydration(h0) { import h._ import c.universe._ type Out[F[_]] = AkkaClientRequestHandler => (F ~> Future) override final def hydrate( - F: Symbol, - nodes: List[NodeInfo] + F: Symbol, + nodes: List[NodeInfo] ): Tree = { val (cases, methods) = nodes .map { info => - val methodName = TermName("handle_" + info.in.typeSymbol.name) - val case_ = cq"""op: ${info.in} => $methodName(op)""" - val method = hydrateMethod(methodName, info.name, info.in, info.out) - (case_, method) - }.unzip(v => v) + val methodName = TermName("handle_" + info.in.typeSymbol.name) + val case_ = cq"""op: ${info.in} => $methodName(op)""" + val method = hydrateMethod(methodName, info.name, info.in, info.out) + (case_, method) + } + .unzip(v => v) q""" @@ -70,10 +82,10 @@ private[h2akka] final class HydrateAkkaHttpClient[H <: HydrationMacros](h0: H) } private[this] final def hydrateMethod( - methodName: TermName, - opName: String, - FA: Type, - A: Type + methodName: TermName, + opName: String, + FA: Type, + A: Type ): Tree = { val needsEncoding = !isSingleton(FA) @@ -93,41 +105,41 @@ private[h2akka] final class HydrateAkkaHttpClient[H <: HydrationMacros](h0: H) } case class AkkaClientRequestHandler( - system : ActorSystem, - baseUri: String = "" + system: ActorSystem, + baseUri: String = "" )(implicit ec: ExecutionContext, mat: Materializer = ActorMaterializer()(system)) { private[this] lazy val http: HttpExt = Http(system) def request[B: Decoder]( - method: HttpMethod, - uri : String + method: HttpMethod, + uri: String ): Future[B] = - request(method, uri, - HttpEntity(ContentTypes.`application/json`, "")) + request(method, uri, HttpEntity(ContentTypes.`application/json`, "")) def request[A, B: Decoder]( - method: HttpMethod, - uri : String, - body : A + method: HttpMethod, + uri: String, + body: A )(implicit encoder: Encoder[A]): Future[B] = - request(method, uri, - HttpEntity(ContentTypes.`application/json`, encoder(body).spaces2)) + request(method, uri, HttpEntity(ContentTypes.`application/json`, encoder(body).spaces2)) def request[B]( - method: HttpMethod, - uri : String, - entity: RequestEntity + method: HttpMethod, + uri: String, + entity: RequestEntity )(implicit decoder: Decoder[B]): Future[B] = { - val request = HttpRequest( - method, s"$baseUri$uri", headers = Nil, entity, HttpProtocols.`HTTP/1.1`) + val request = + HttpRequest(method, s"$baseUri$uri", headers = Nil, entity, HttpProtocols.`HTTP/1.1`) try { val responseFuture: Future[HttpResponse] = http.singleRequest(request) responseFuture .flatMap(response => Unmarshal(response.entity).to[String]) - .map (data => { - parseJSON(data).flatMap(decoder.decodeJson) - .fold(error => throw error, b => b) }) + .map(data => { + parseJSON(data) + .flatMap(decoder.decodeJson) + .fold(error => throw error, b => b) + }) } catch { case e: Throwable => Future.failed(e) } diff --git a/modules/http-akka/src/main/scala/mezzo/h2akka/AkkaHttpRoutes.scala b/http/akka/src/main/scala/AkkaHttpRoutes.scala similarity index 65% rename from modules/http-akka/src/main/scala/mezzo/h2akka/AkkaHttpRoutes.scala rename to http/akka/src/main/scala/AkkaHttpRoutes.scala index 99f1e9dfe..3a7cc8b75 100644 --- a/modules/http-akka/src/main/scala/mezzo/h2akka/AkkaHttpRoutes.scala +++ b/http/akka/src/main/scala/AkkaHttpRoutes.scala @@ -1,49 +1,57 @@ -/* - - * Mezzo [http-akka] +/* + * Copyright 2017 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ -package mezzo +package freestyle +package http package h2akka import io.circe.Decoder import io.circe.Encoder import io.circe.Json import io.circe.Printer - import cats._ - import akka.http.scaladsl.server.Route import akka.util.ByteString import io.circe.jawn import scala.concurrent.Future - -import akka.http.scaladsl.marshalling.{ Marshaller, ToEntityMarshaller } +import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller} import akka.http.scaladsl.model.HttpEntity - import akka.http.scaladsl.model.MediaTypes.`application/json` -import akka.http.scaladsl.unmarshalling.{ FromEntityUnmarshaller, Unmarshaller } +import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller} +import freestyle.http.core._ private[h2akka] final class HydrateAkkaHttpRoutes[H <: HydrationMacros](h0: H) - extends Hydration(h0) -{ + extends Hydration(h0) { import h._ import c.universe._ type Out[F[_]] = (F ~> Future) => Route override final def hydrate( - F: Symbol, - nodes: List[NodeInfo] + F: Symbol, + nodes: List[NodeInfo] ): Tree = - q""" import cats.arrow.FunctionK import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.RouteConcatenation.concat import akka.http.scaladsl.model.HttpEntity import scala.concurrent.Future - import mezzo.h2akka.HydrateAkkaHttpRoutesSupport._ + import freestyle.http.h2akka.HydrateAkkaHttpRoutesSupport._ (eval: FunctionK[$F, Future]) => concat( ..${nodes.map(info => makeRoute(info.name, info.in, info.out))}) @@ -69,13 +77,14 @@ private[h2akka] final class HydrateAkkaHttpRoutes[H <: HydrationMacros](h0: H) $encode """ - val decode = if (needsDecoding) - q""" + val decode = + if (needsDecoding) + q""" implicit val um = makeUnmarshaller[$FA] entity(as[$FA]) { op => $complete } """ - else - q""" + else + q""" val op = ${FA.termSymbol} $complete """ @@ -96,9 +105,9 @@ object HydrateAkkaHttpRoutesSupport { Unmarshaller.byteStringUnmarshaller .forContentTypes(`application/json`) .map { - case ByteString.empty => throw Unmarshaller.NoContentException - case data => jawn.parseByteBuffer(data.asByteBuffer).fold(throw _, v => v) - } + case ByteString.empty => throw Unmarshaller.NoContentException + case data => jawn.parseByteBuffer(data.asByteBuffer).fold(throw _, v => v) + } final def makeMarshaller[A: Encoder]: ToEntityMarshaller[A] = jsonMarshaller.compose(Encoder[A].apply) diff --git a/http/akka/src/main/scala/package.scala b/http/akka/src/main/scala/package.scala new file mode 100644 index 000000000..20ea75f88 --- /dev/null +++ b/http/akka/src/main/scala/package.scala @@ -0,0 +1,30 @@ +/* + * Copyright 2017 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package freestyle +package http + +import freestyle.http.core.Dehydrated + +package object h2akka { + type AkkaHttpClient = Dehydrated { + type H = HydrateAkkaHttpClient[_] + } + + type AkkaHttpRoutes = Dehydrated { + type H = HydrateAkkaHttpRoutes[_] + } +} diff --git a/http/core/src/main/scala-2.11/package.scala b/http/core/src/main/scala-2.11/package.scala new file mode 100644 index 000000000..db71b8b43 --- /dev/null +++ b/http/core/src/main/scala-2.11/package.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2017 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package freestyle +package http + +package object core { + implicit class EitherCompatOps[A, B](val either: Either[A, B]) extends AnyVal { + def flatMap[AA >: A, C](f: B => Either[AA, C]): Either[AA, C] = + either.right.flatMap(f) + def map[AA >: A, C](f: B => C): Either[AA, C] = + either.right.map(f) + } +} diff --git a/http/core/src/main/scala/algebra.scala b/http/core/src/main/scala/algebra.scala new file mode 100644 index 000000000..a57a812b2 --- /dev/null +++ b/http/core/src/main/scala/algebra.scala @@ -0,0 +1,74 @@ +/* + * Copyright 2017 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package freestyle +package http +package core + +import scala.Predef.<:< +import scala.reflect.ClassTag + +import shapeless._ +import shapeless.labelled._ +import shapeless.tag._ + +sealed trait Algebra[F[_]] { + type Out <: HList +} + +object Algebra { + type Aux[F[_], O] = Algebra[F] { type Out = O } + + type Node[I, N, O] + + implicit final def algebra[F[_], CF <: Coproduct, LF <: HList, O <: HList]( + implicit gen: LabelledGeneric.Aux[F[_], CF], + ev: ops.coproduct.ToHList.Aux[CF, LF], + build: BuildAlgebra.Aux[F, LF, O] + ): Algebra.Aux[F, O] = + new Algebra[F] { type Out = O } + + // @annotation.inductive // leave off until backport/lazy support? + sealed private[Algebra] abstract class BuildAlgebra[F[_], R] private[this] { + type Out <: HList + } + + private[Algebra] object BuildAlgebra { + type Aux[F[_], R, O] = BuildAlgebra[F, R] { type Out = O } + + implicit final def buildAlgebraHNil[ + F[_] + ]: BuildAlgebra.Aux[F, HNil, HNil] = + thereIsNoSpoon + + implicit final def buildAlgebraHCons[ + F[_], + A, + FA: ? <:< F[A], + S, + K: ? <:< (Symbol @@ S), + RT <: HList, + T <: HList + ]( + implicit tail: BuildAlgebra.Aux[F, RT, T], + wit: Witness.Aux[K], + ctA: ClassTag[A]): BuildAlgebra.Aux[F, FieldType[K, FA] :: RT, Node[FA, S, A] :: T] = + thereIsNoSpoon + + private[this] final def thereIsNoSpoon[A]: A = null.asInstanceOf[A] + } + +} diff --git a/modules/core/src/main/scala/mezzo/hydrate.scala b/http/core/src/main/scala/hydrate.scala similarity index 51% rename from modules/core/src/main/scala/mezzo/hydrate.scala rename to http/core/src/main/scala/hydrate.scala index 4f7b3ea8b..c4165715a 100644 --- a/modules/core/src/main/scala/mezzo/hydrate.scala +++ b/http/core/src/main/scala/hydrate.scala @@ -1,8 +1,22 @@ -/* - - * Mezzo [core] +/* + * Copyright 2017 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ -package mezzo +package freestyle +package http +package core import scala.annotation.tailrec import scala.reflect.macros.whitebox @@ -13,7 +27,7 @@ import cats.instances.all._ import shapeless._ final class Hydrate[D <: Dehydrated] { - def hydrate[F[_]](implicit alg: Algebra[F]): D#H#Out[F] = + def hydrate[F[_]](implicit alg: Algebra[F]): D#H#Out[F] = macro HydrationMacros.hydrate[D, F] } @@ -49,73 +63,70 @@ final class HydrationMacros(val c: whitebox.Context) { import c.universe._ def hydrate[D <: Dehydrated, F[_]](alg: c.Expr[Algebra[F]])( - implicit - evD: c.WeakTypeTag[D], + implicit evD: c.WeakTypeTag[D], evF: c.WeakTypeTag[F[_]] ): c.Expr[D#H#Out[F]] = ( for { - FSym <- typeRefToSym(evF.tpe) - aux <- typeToAuxType(alg.tree.tpe) - nodeTypes <- hlistTypeToTypeList(aux) - nodeInfos <- Traverse[List].traverse(nodeTypes)(nodeTypeToNodeInfo) - hydration <- locateHydration(evD.tpe) - _ = scala.Predef.println( - s"Today's hydration brought to you by ${hydration.getClass}") - } yield - hydration.hydrate(FSym, nodeInfos) - - ) fold ( - error => c.abort(c.enclosingPosition, error), - tree => c.Expr[D#H#Out[F]](tree)) - - - import scala.reflect.runtime.{ universe => ru } + FSym <- typeRefToSym(evF.tpe) + aux <- typeToAuxType(alg.tree.tpe) + nodeTypes <- hlistTypeToTypeList(aux) + nodeInfos <- Traverse[List].traverse(nodeTypes)(nodeTypeToNodeInfo) + hydration <- locateHydration(evD.tpe) + _ = scala.Predef.println(s"Today's hydration brought to you by ${hydration.getClass}") + } yield hydration.hydrate(FSym, nodeInfos) + ) fold (error => c.abort(c.enclosingPosition, error), + tree => c.Expr[D#H#Out[F]](tree)) + + import scala.reflect.runtime.{universe => ru} lazy val m = ru.runtimeMirror(getClass.getClassLoader) case class NodeInfo(in: Type, name: String, out: Type) - private[this] val ShapelessSym = typeOf[HList].typeSymbol.owner - private[this] val HNilSym = typeOf[HNil].typeSymbol - private[this] val HConsSym = typeOf[shapeless.::[_, _]].typeSymbol - private[this] val NodeSym = typeOf[Algebra.Node[_, _, _]].typeSymbol - private[this] val NodeSymOwner = typeOf[Algebra.Node[_, _, _]].typeSymbol.owner - private[this] val MezzoSym = typeOf[Algebra[Nothing]].typeSymbol.owner - private[this] val HTypeName = TypeName("H") + private[this] val ShapelessSym = typeOf[HList].typeSymbol.owner + private[this] val HNilSym = typeOf[HNil].typeSymbol + private[this] val HConsSym = typeOf[shapeless.::[_, _]].typeSymbol + private[this] val NodeSym = typeOf[Algebra.Node[_, _, _]].typeSymbol + private[this] val NodeSymOwner = typeOf[Algebra.Node[_, _, _]].typeSymbol.owner + private[this] val HTypeName = TypeName("H") @tailrec - final def hlistTypeFoldLeft[A](tpe: Type)(a0: A)(f: (A, Type) => A): Either[String, A] = tpe match { - case TypeRef(ThisType(ShapelessSym), HNilSym, Nil) => Right(a0) - case TypeRef(ThisType(ShapelessSym), HConsSym, List(headType, tailType)) => - hlistTypeFoldLeft(tailType)(f(a0, headType))(f) - case _ => - Left("Unexpected type when inspecting HList") - } + final def hlistTypeFoldLeft[A](tpe: Type)(a0: A)(f: (A, Type) => A): Either[String, A] = + tpe match { + case TypeRef(ThisType(ShapelessSym), HNilSym, Nil) => Right(a0) + case TypeRef(ThisType(ShapelessSym), HConsSym, List(headType, tailType)) => + hlistTypeFoldLeft(tailType)(f(a0, headType))(f) + case _ => + Left("Unexpected type when inspecting HList") + } private[this] final def hlistTypeToTypeList(tpe: Type): Either[String, List[Type]] = hlistTypeFoldLeft(tpe)(List.empty[Type])((acc, t) => t :: acc) def typeToAuxType(tpe: Type): Either[String, Type] = tpe.dealias match { - case RefinedType(parents, decls) => decls.headOption match { - case Some(head: TypeSymbol) => Right(head.toType.dealias) - case _ => - Left("Unable to extract dependant type from Algebra.Aux") - } - case _ => + case RefinedType(parents, decls) => + decls.headOption match { + case Some(head: TypeSymbol) => Right(head.toType.dealias) + case _ => + Left("Unable to extract dependant type from Algebra.Aux") + } + case _ => Left("Expected a refined type") } private[this] final def nodeTypeToNodeInfo(tpe: Type): Either[String, NodeInfo] = tpe match { - case TypeRef(ThisType(NodeSymOwner), NodeSym, - List(in, ConstantType(Constant(name: String)), out)) => - Right(NodeInfo(in, name, out)) + case TypeRef( + ThisType(NodeSymOwner), + NodeSym, + List(in, ConstantType(Constant(name: String)), out)) => + Right(NodeInfo(in, name, out)) case _ => Left(s"unexpected node type $tpe") } private[this] final def typeRefToSym(tpe: Type): Either[String, Symbol] = tpe match { case TypeRef(pre, sym: TypeSymbol, Nil) => Right(sym) - case _ => + case _ => Left(s"unable to extract TypeRef symbol for $tpe") } diff --git a/modules/demo/src/main/scala/mezzodemo/demo.scala b/http/demo/src/main/scala/demo.scala similarity index 65% rename from modules/demo/src/main/scala/mezzodemo/demo.scala rename to http/demo/src/main/scala/demo.scala index 28b50d900..ea4561b0b 100644 --- a/modules/demo/src/main/scala/mezzodemo/demo.scala +++ b/http/demo/src/main/scala/demo.scala @@ -1,13 +1,27 @@ -/* - - * Mezzo [demo] +/* + * Copyright 2017 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ -package mezzodemo +package freestyle +package http +package demo import scala.Predef._ -import mezzo.Hydrate -import mezzo.h2akka._ +import freestyle.http.core.Hydrate +import freestyle.http.h2akka._ import cats._ import io.circe.generic.auto._ @@ -22,22 +36,21 @@ import scala.concurrent.Future import scala.concurrent.duration._ import scala.util.Try - // a simple demo API facade case class CounterAPI(eval: CounterOp ~> Future) { import CounterOp._ def adjust(delta: Long): Future[Long] = eval(Adjust(delta)) - def reset() : Future[Unit] = eval(Reset) - def read() : Future[Long] = eval(Read) + def reset(): Future[Unit] = eval(Reset) + def read(): Future[Long] = eval(Read) } // the actual algebra for our API (I'd actually call this the API) sealed abstract class CounterOp[A] extends Product with Serializable object CounterOp { case class Adjust(delta: Long) extends CounterOp[Long] - case object Reset extends CounterOp[Unit] - case object Read extends CounterOp[Long] + case object Reset extends CounterOp[Unit] + case object Read extends CounterOp[Long] } // an implementation of the API @@ -81,13 +94,13 @@ object DummyApp extends App { val counter = CounterAPI(client) // interact with our service over HTTP - println("---> " + Try(Await.result(counter.read(), 10.seconds))) + println("---> " + Try(Await.result(counter.read(), 10.seconds))) println("---> " + Try(Await.result(counter.adjust(100), 10.seconds))) - println("---> " + Try(Await.result(counter.reset(), 10.seconds))) + println("---> " + Try(Await.result(counter.reset(), 10.seconds))) println("---> " + Try(Await.result(counter.adjust(200), 10.seconds))) - println("---> " + Try(Await.result(counter.read(), 10.seconds))) + println("---> " + Try(Await.result(counter.read(), 10.seconds))) println("---> " + Try(Await.result(counter.adjust(300), 10.seconds))) - println("---> " + Try(Await.result(counter.read(), 10.seconds))) + println("---> " + Try(Await.result(counter.read(), 10.seconds))) // shutdown the world system.terminate() diff --git a/modules/core/src/main/scala/mezzo/algebra.scala b/modules/core/src/main/scala/mezzo/algebra.scala deleted file mode 100644 index 862961d54..000000000 --- a/modules/core/src/main/scala/mezzo/algebra.scala +++ /dev/null @@ -1,58 +0,0 @@ -/* - - * Mezzo [core] - */ - -package mezzo - -import scala.Predef.<:< -import scala.reflect.ClassTag - -import shapeless._ -import shapeless.labelled._ -import shapeless.tag._ - -sealed trait Algebra[F[_]] { - type Out <: HList -} - -object Algebra { - type Aux[F[_], O] = Algebra[F] { type Out = O } - - type Node[I, N, O] - - implicit final def algebra[F[_], CF <: Coproduct, LF <: HList, O <: HList]( - implicit - gen : LabelledGeneric.Aux[F[_], CF], - ev : ops.coproduct.ToHList.Aux[CF, LF], - build: BuildAlgebra.Aux[F, LF, O] - ): Algebra.Aux[F, O] = - new Algebra[F] { type Out = O } - - // @annotation.inductive // leave off until backport/lazy support? - sealed private[Algebra] abstract class BuildAlgebra[F[_], R] private[this] { - type Out <: HList - } - - private[Algebra] object BuildAlgebra { - type Aux[F[_], R, O] = BuildAlgebra[F, R] { type Out = O } - - implicit final def buildAlgebraHNil[ - F[_] - ]: BuildAlgebra.Aux[F, HNil, HNil] = - thereIsNoSpoon - - implicit final def buildAlgebraHCons[ - F[_], A, FA: ? <:< F[A], - S, K: ? <:< (Symbol @@ S), - RT <: HList, T <: HList - ](implicit - tail: BuildAlgebra.Aux[F, RT, T], - wit : Witness.Aux[K], - ctA : ClassTag[A] - ): BuildAlgebra.Aux[F, FieldType[K, FA] :: RT, Node[FA, S, A] :: T] = - thereIsNoSpoon - - private[this] final def thereIsNoSpoon[A]: A = null.asInstanceOf[A] - } - -} diff --git a/modules/core/src/main/scala_2.11/mezzo/package.scala b/modules/core/src/main/scala_2.11/mezzo/package.scala deleted file mode 100644 index 6ba3810f4..000000000 --- a/modules/core/src/main/scala_2.11/mezzo/package.scala +++ /dev/null @@ -1,12 +0,0 @@ -/* - - * Mezzo [core] - */ - -package object mezzo { - private[mezzo] implicit class EitherCompatOps[A, B](val either: Either[A, B]) extends AnyVal { - def flatMap[AA >: A, C](f: B => Either[AA, C]): Either[AA, C] = - either.right.flatMap(f) - def map[AA >: A, C](f: B => C): Either[AA, C] = - either.right.map(f) - } -} diff --git a/modules/http-akka/src/main/scala/mezzo/h2akka/package.scala b/modules/http-akka/src/main/scala/mezzo/h2akka/package.scala deleted file mode 100644 index 4354d96e2..000000000 --- a/modules/http-akka/src/main/scala/mezzo/h2akka/package.scala +++ /dev/null @@ -1,15 +0,0 @@ -/* - - * Mezzo [http-akka] - */ - -package mezzo - -package object h2akka { - type AkkaHttpClient = Dehydrated { - type H = HydrateAkkaHttpClient[_] - } - - type AkkaHttpRoutes = Dehydrated { - type H = HydrateAkkaHttpRoutes[_] - } -} diff --git a/modules/readme/src/main/tut/README.md b/modules/readme/src/main/tut/README.md deleted file mode 100644 index 8b1739178..000000000 --- a/modules/readme/src/main/tut/README.md +++ /dev/null @@ -1,136 +0,0 @@ -### Mezzo -[![Build Status](https://api.travis-ci.org/andyscott/mezzo.png?branch=master)](https://travis-ci.org/andyscott/mezzo) - -```tut:invisible -import akka.actor.ActorSystem -import akka.stream.ActorMaterializer -import scala.concurrent.Await -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future -import scala.concurrent.duration._ - -implicit val system = ActorSystem("testing") -implicit val mat = ActorMaterializer() -``` - -### Introduction - -Mezzo is a hydration framework for watering and growing your code from a -definition of your API. - -What does this mean? - -Consider a simple API for a counter: - -```tut:silent -import scala.concurrent.Future - -trait CounterAPI { - def adjust(delta: Long): Future[Long] - def reset() : Future[Unit] - def read() : Future[Long] -} -``` - -This is a very traditional looking API. What we really want to -do is model our API as a series of case classes (an ADT). This will -allow to get a lot of work done for free. - -```tut:silent -sealed abstract class CounterOp[A] extends Product with Serializable -object CounterOp { - case class Adjust(delta: Long) extends CounterOp[Long] - case object Reset extends CounterOp[Unit] - case object Read extends CounterOp[Long] -} -``` - -If we want, we can implement the traditional interface by lifting our ADT -operations into our return type of Future. We can do this with a natural -transformation (`~>`). - -```tut:silent -import cats.~> - -case class CounterOps(eval: CounterOp ~> Future) extends CounterAPI { - import CounterOp._ - - def adjust(delta: Long): Future[Long] = eval(Adjust(delta)) - def reset() : Future[Unit] = eval(Reset) - def read() : Future[Long] = eval(Read) -} -``` - -We can go ahead and implement our API using a natural transformation. -This is a lot like writing the body of an Actor, except the body is -strongly typed. - -```tut:silent -class DummyCounter extends (CounterOp ~> Future) { - - override def apply[A](rawOp: CounterOp[A]): Future[A] = rawOp match { - case op: CounterOp.Adjust => handleAdjust(op) - case CounterOp.Reset => handleReset() - case CounterOp.Read => handleRead() - } - - @volatile private[this] var count: Long = 0 - - private[this] def handleAdjust(op: CounterOp.Adjust): Future[Long] = - Future { count = count + op.delta; count } - private[this] def handleReset(): Future[Unit] = - Future { count = 0 } - private[this] def handleRead(): Future[Long] = - Future { count } -} -``` - -We can now instantly hydrate a HTTP server and HTTP client for our API. - -```tut:silent -import mezzo.Hydrate -import mezzo.h2akka._ -import io.circe.generic.auto._ -``` - -*server:* -```tut:silent -import akka.http.scaladsl.Http - -val backend = new DummyCounter() -val routes = Hydrate[AkkaHttpRoutes].hydrate[CounterOp].apply(backend) -val binding = Http().bindAndHandle(routes, "localhost", 8080) -``` - -*client:* -```tut:silent -import akka.http.scaladsl.Http - -val handler = AkkaClientRequestHandler(system, "http://localhost:8080/") -val client = Hydrate[AkkaHttpClient].hydrate[CounterOp].apply(handler) -val counter = CounterOps(client) -``` - -And the client works as expected: - -```tut:book -Await.result(counter.read(), 10.seconds) -Await.result(counter.adjust(1), 10.seconds) -Await.result(counter.adjust(10), 10.seconds) -Await.result(counter.adjust(100), 10.seconds) -Await.result(counter.read(), 10.seconds) -Await.result(counter.reset(), 10.seconds) -Await.result(counter.read(), 10.seconds) -``` - -```tut:invisible -system.terminate() -``` -### Documentation - -Documetation coming soon. - -### License -The license can be found in [COPYING]. - -[COPYING]: COPYING diff --git a/project/build.properties b/project/build.properties index 27e88aa11..64317fdae 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.13 +sbt.version=0.13.15 diff --git a/project/common.scala b/project/common.scala deleted file mode 100644 index 726f321ee..000000000 --- a/project/common.scala +++ /dev/null @@ -1,141 +0,0 @@ -import sbt.Keys._ -import sbt._ - -import de.heikoseeberger.sbtheader.AutomateHeaderPlugin -import de.heikoseeberger.sbtheader.HeaderPattern -import de.heikoseeberger.sbtheader.HeaderPlugin -import de.heikoseeberger.sbtheader.HeaderKey.headers -import com.typesafe.sbt.SbtPgp.autoImport._ -//import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ - -import scala.{ Console => C } - -object BuildCommon extends AutoPlugin { - - override def requires = plugins.JvmPlugin && HeaderPlugin - override def trigger = allRequirements - - object autoImport { - lazy val noPublishSettings = Seq( - publish := (), - publishLocal := (), - publishArtifact := false) - } - - override def projectSettings = - baseSettings ++ - enhancingScalaSettings ++ - publishSettings ++ - AutomateHeaderPlugin.projectSettings - - private[this] def baseSettings = Seq( - scalaVersion := "2.12.1", - scalaOrganization := "org.typelevel", - crossScalaVersions := Seq("2.11.8", "2.12.0"), - - organization := "xyz.anamorph", - description := "ADT driven clients and adapters", - - fork in run := true, - fork in Test := true, - //fork in Test := !isScalaJSProject.value, - parallelExecution in Test := false, - outputStrategy := Some(StdoutOutput), - connectInput in run := true, - cancelable in Global := true, - - scalacOptions ++= Seq( - "-deprecation", - "-encoding", "UTF-8", - "-feature", - "-language:existentials", - "-language:higherKinds", - "-language:implicitConversions", - "-language:experimental.macros", - "-unchecked", - //"-Xfatal-warnings", - "-Xlint", - "-Yno-adapted-args", - "-Ywarn-dead-code", - "-Ywarn-numeric-widen", - "-Ywarn-value-discard", - "-Ywarn-unused-import", - "-Xfuture", - "-Yno-predef", - "-Ypartial-unification"), - - scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 11)) => Nil - case Some((2, 12)) => Seq("-Yliteral-types") - case _ => Nil - }), - - libraryDependencies ++= (CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 11)) => Seq( - compilerPlugin("com.milessabin" % "si2712fix-plugin" % "1.2.0" cross CrossVersion.full)) - case Some((2, 12)) => Nil - case _ => Nil - }), - - scalacOptions in (Compile, doc) := - (scalacOptions in (Compile, doc)).value.filter(_ != "-Xfatal-warnings"), - - headers := Map( - "scala" → ( - HeaderPattern.cStyleBlockComment, - s"""|/* - - | * Mezzo [${name.value}] - | */ - | - |""".stripMargin)) - ) - - private[this] def enhancingScalaSettings = Seq( - //resolvers += Resolver.bintrayRepo("tek", "maven"), - //libraryDependencies ++= Seq( - //compilerPlugin("tryp" %% "splain" % "0.1.11")), - resolvers += Resolver.sonatypeRepo("releases"), - libraryDependencies ++= Seq( - compilerPlugin( - "org.spire-math" %% "kind-projector" % "0.9.3" cross CrossVersion.binary) - ) - ) - - private[this] lazy val gpgFolder = sys.env.getOrElse("GPG_FOLDER", ".") - - private[this] lazy val publishSettings = Seq( - pgpPassphrase := Some(sys.env.getOrElse("GPG_PASSPHRASE", "").toCharArray), - pgpPublicRing := file(s"$gpgFolder/pubring.gpg"), - pgpSecretRing := file(s"$gpgFolder/secring.gpg"), - credentials += Credentials("Sonatype Nexus Repository Manager", - "oss.sonatype.org", - sys.env.getOrElse("PUBLISH_USERNAME", ""), - sys.env.getOrElse("PUBLISH_PASSWORD", "")), - scmInfo := Some(ScmInfo( - url("https://github.com/andyscott/mezzo"), - "https://github.com/andyscott/mezzo.git")), - startYear := Some(2016), - homepage := Option(url("https://github.com/andyscott/mezzo")), - organizationHomepage := Option(new URL("http://mezzo.anamorph.xyz")), - licenses := Seq("Apache License, Version 2.0" -> - url("http://www.apache.org/licenses/LICENSE-2.0.txt")), - publishMavenStyle := true, - publishArtifact in Test := false, - pomIncludeRepository := Function.const(false), - publishTo := { - val nexus = "https://oss.sonatype.org/" - if (isSnapshot.value) - Some("Snapshots" at nexus + "content/repositories/snapshots") - else - Some("Releases" at nexus + "service/local/staging/deploy/maven2") - }, - pomExtra := - - - Andy Scott - andy.g.scott@gmail.com - - - ) - -} diff --git a/project/plugins.sbt b/project/plugins.sbt old mode 100755 new mode 100644 index d8df1fa41..dd62ebd7b --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,2 @@ -addSbtPlugin("de.heikoseeberger" % "sbt-header" % "1.5.1") -addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.8") -addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.3.3") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.0") -addSbtPlugin("com.fortysevendeg" % "sbt-microsites" % "0.4.0") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") -addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-M15") +resolvers ++= Seq(Resolver.sonatypeRepo("snapshots"), Resolver.sonatypeRepo("releases")) +addSbtPlugin("io.frees" % "sbt-freestyle" % "0.0.1-SNAPSHOT") \ No newline at end of file diff --git a/project/project/plugins.sbt b/project/project/plugins.sbt index 5b48d85fb..dfe274c01 100644 --- a/project/project/plugins.sbt +++ b/project/project/plugins.sbt @@ -1 +1 @@ -addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-M15") +addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC1") diff --git a/pubring.gpg b/pubring.gpg new file mode 100644 index 000000000..33e416b09 --- /dev/null +++ b/pubring.gpg @@ -0,0 +1,18 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: BCPG v1.51 + +mQENBFgaZ4EBCADA/g+xxG8rADqhyXfZ7Uxx4VbOR0IPKSUSOlQyVX6aywUIHHaj +C+DqJ6kR/vBpLbrfi5ZSAtXyp8LYn6e1HQaGlVhYpRLflvicOASfHW2+idVLT3X1 +uI26mr3VqUUb4BAMB5d0UCuVCmcgh27jf5gGxTFC0SjFPtjtiNTsZYgz5/XCOvmP +PbIowTcloxtuiq1u/7FIL8p502AxHAse6Dmnc+f0HhGesBsCW6wSppNkL7aw1kQF +LfrGuXGRtT2yYbLvyIpPQFsGA7ZVU8cXS4MJOF+hNpIHejzQVonm6r8P3CTuuBHc +FvjL/t5yfkxFhrf84C0QmHNANI1z2kgObc/RABEBAAG0JTQ3ZGVnL2ZyZWVzdHls +ZSA8ZGV2ZWxvcGVyQDQ3ZGVnLmNvbT6JARwEEwECAAYFAlgaZ4EACgkQaSNddcix +bxbeTwf9HbmCgraCyMJvhV3YxYrPAnYtTVRZQRRMAG4RsFa95l8DsrolfHJOObvL +z9zwb3WiRi2U+SXSpKUJQkZSJRFGhMxJvCzBD+YBt1I0XIm5a49CXqfi/gqbVJD7 +3aqZVv8WSuEuSXT1eIhV4z/HefvmMU9VB2StTfCoGGzsQ+BgP9v7Q+GRwsUCAntO +YnFE6r0EZrT+wvrApsgOdBLiGMvTm/FkOb6QHesDGo18YjgZzK1zs/r7dkY4TG+Y +vE0mX3uZyxT9KmDtHLrzZ0jtOPWvnLklR/BujjzDVEWsyK/HBV+Y4Ef/7gaDpigm +svPdhWroMXwc+Kyzn4gGIXk/9HYl7A== +=U4e2 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/secring.gpg.enc b/secring.gpg.enc new file mode 100644 index 000000000..41254cf39 Binary files /dev/null and b/secring.gpg.enc differ diff --git a/version.sbt b/version.sbt new file mode 100644 index 000000000..d63f66ac2 --- /dev/null +++ b/version.sbt @@ -0,0 +1 @@ +version in ThisBuild := "0.0.1-SNAPSHOT" \ No newline at end of file