diff --git a/.travis.yml b/.travis.yml index 00f01cb4..df2254a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,12 @@ scala: - 2.12.6 - 2.11.12 +script: +- sbt ++$TRAVIS_SCALA_VERSION orgScriptCI + +after_success: +- sbt ++$TRAVIS_SCALA_VERSION orgAfterCISuccess + before_cache: - find $HOME/.sbt -name "*.lock" -type f -delete - find $HOME/.ivy2/cache -name "ivydata-*.properties" -type f -delete diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 00000000..e65756a7 --- /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 Skeuomorph project: + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..825c32f0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..376f4e9c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing + +Discussion around Skeuomorph happens in the [Gitter channel](https://gitter.im/frees-io/skeuomorph) as well as on +[GitHub issues](https://github.com/frees-io/skeuomorph/issues) and [pull requests](https://github.com/frees-io/skeuomorph/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 Skeuomorph 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? + +Skeuomorph 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/LICENSE b/LICENSE new file mode 100644 index 00000000..b1a145b1 --- /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) 2018 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 00000000..2a2c43c5 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,4 @@ +Skeuomorph +Copyright (c) 2018 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 a91cbe3a..ba14e2b3 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,104 @@ +[comment]: # (Start Badges) + +[![Build Status](https://travis-ci.org/frees-io/skeuomorph.svg?branch=master)](https://travis-ci.org/frees-io/skeuomorph) [![codecov.io](http://codecov.io/github/frees-io/skeuomorph/coverage.svg?branch=master)](http://codecov.io/github/frees-io/skeuomorph?branch=master) [![Maven Central](https://img.shields.io/badge/maven%20central-0.1.0-green.svg)](https://oss.sonatype.org/#nexus-search;gav~io.frees~skeuomorph*) [![Latest version](https://img.shields.io/badge/skeuomorph-0.1.0-green.svg)](https://index.scala-lang.org/frees-io/skeuomorph) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/frees-io/skeuomorph/master/LICENSE) [![Join the chat at https://gitter.im/47deg/skeuomorph](https://badges.gitter.im/47deg/skeuomorph.svg)](https://gitter.im/47deg/skeuomorph?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GitHub Issues](https://img.shields.io/github/issues/frees-io/skeuomorph.svg)](https://github.com/frees-io/skeuomorph/issues) + +[comment]: # (End Badges) + + # Skeuomorph -Skeuomorph is for schema transformation. +Skeuomorph is a library for transforming different schemas in Scala. +It provides schema definitions as non-recursive ADTs, and +transformations & optimizations via recursion schemes. + +This library is primarilly intended to be used at [freestyle-rpc][], but +it's completely independent from it, so anybody can use it. + +Skeuomorph depends heavily on [cats][] and [droste][]. ## Schemas -Currently skeuomorph supports 3 schemas: -- Avro -- Protobuf -- Freestyle +Currently skeuomorph supports 3 different schemas: +- [Avro][] +- [Protobuf][] +- [freestyle-rpc][] + +And provides conversions between them. This means that you can get a +`org.apache.avro.Schema` value, and convert it to protobuf, for +example. Or to a freestyle service description. + + +## Installation + +You can install skeuomorph as follows: -and has conversions between them. +[comment]: # (Start Replace) +```scala +libraryDependencies += "io.frees" %% "skeuomorph" % "0.1.0" +``` + +[comment]: # (End Replace) ## Examples ### parsing an avro schema and then converting it to scala: -```scala -import java.io.File +```tut import org.apache.avro._ import skeuomorph._ -import skeuomorph.freestyle.service._ import turtles._ import turtles.data.Mu import turtles.implicits._ -val schema: Schema = new Schema.Parser().parse(new File("user.avsc")); +val definition = """ +{ + "namespace": "example.avro", + "type": "record", + "name": "User", + "fields": [ + { + "name": "name", + "type": "string" + }, + { + "name": "favorite_number", + "type": [ + "int", + "null" + ] + }, + { + "name": "favorite_color", + "type": [ + "string", + "null" + ] + } + ] +} + """ + +val schema: Schema = new Schema.Parser().parse(definition) -schema.ana[Mu[avro.Schema]](avro.util.fromAvro) // org.apache.avro.Schema => skeuomorph.avro.Schema - .transCata[Mu[freestyle.Schema]](freestyle.utils.transformAvro) // skeuomorph.avro.Schema => skeuomorph.freestyle.Schema - .cata(freestyle.utils.render) // skeuomorph.freestyle.Schema => String +schema. + ana[Mu[avro.Schema]](avro.util.fromAvro). // org.apache.avro.Schema => skeuomorph.avro.Schema. + transCata[Mu[freestyle.Schema]](freestyle.util.transformAvro). // skeuomorph.avro.Schema => skeuomorph.freestyle.Schema. + cata(freestyle.util.render) // skeuomorph.freestyle.Schema => String ``` + + +## Skeuomorph in the wild + +If you wish to add your library here please consider a PR to include +it in the list below. + +| **Name** | **Description** | +|-----------------------------------------------|----------------------------------------------------------------------------------------------------| +| [**freestyle-rpc**](http://frees.io/docs/rpc) | purely functional library for building RPC endpoint based services with support for RPC and HTTP/2 | + +[Avro]: https://avro.apache.org/ +[Protobuf]: https://developers.google.com/protocol-buffers/ +[freestyle-rpc]: http://frees.io/docs/rpc/quickstart +[cats]: http://typelevel.org/cats +[droste]: http://github.com/andyscott/droste diff --git a/build.sbt b/build.sbt index 9b34d2bc..159b7661 100644 --- a/build.sbt +++ b/build.sbt @@ -1,25 +1,48 @@ -lazy val core = project +import microsites._ +import sbtorgpolicies.OrgPoliciesPlugin.autoImport._ +import sbtorgpolicies.templates.badges._ +import sbtorgpolicies.templates._ + + +lazy val root = project .in(file(".")) .settings(commonSettings) .settings( name := "skeuomorph" ) -val catsV = "1.1.0" -val kittensV = "1.1.0" -val catsEffectV = "0.10.1" -val mouseV = "0.17" -val shapelessV = "2.3.2" -val fs2V = "0.10.5" -val http4sV = "0.18.14" -val circeV = "0.9.3" -val doobieV = "0.5.3" -val pureConfigV = "0.9.1" -val refinedV = "0.9.1" - -val specs2V = "4.2.0" -val disciplineV = "0.8" -val scShapelessV = "1.1.6" +lazy val docs = project + .in(file("docs")) + .dependsOn(root) + .settings(moduleName := "skeuomorph-docs") + .settings(commonSettings) + .settings(compilerPlugins) + .settings(noPublishSettings) + .settings( + micrositeName := "Skeuomorph", + micrositeDescription := "Schema transformations", + micrositeBaseUrl := "frees.io/skeuomorph", + micrositeGithubOwner := "frees-io", + micrositeGithubRepo := "skeuomorph", + micrositeHighlightTheme := "tomorrow", + micrositePushSiteWith := GitHub4s, + micrositeGithubToken := sys.env.get("GITHUB_TOKEN"), + micrositeExtraMdFiles := Map( + file("README.md") -> ExtraMdFileConfig( + "index.md", + "home", + Map("title" -> "Home", "section" -> "home", "position" -> "0") + ), + file("CHANGELOG.md") -> ExtraMdFileConfig( + "changelog.md", + "home", + Map("title" -> "changelog", "section" -> "changelog", "position" -> "99") + ) + ), + scalacOptions in Tut ~= filterConsoleScalacOptions, + scalacOptions in Tut += "-language:postfixOps" + ) + .enablePlugins(MicrositesPlugin) lazy val contributors = Seq( "pepegar" -> "Pepe Garcia" @@ -32,166 +55,41 @@ onLoad in Global := { s => // General Settings lazy val commonSettings = Seq( - organization := "com.pepegar", + organization := "io.frees", scalaVersion := "2.12.6", + startYear := Some(2018), crossScalaVersions := Seq(scalaVersion.value, "2.11.12"), - scalafmtOnCompile in ThisBuild := true, - addCompilerPlugin("org.spire-math" % "kind-projector" % "0.9.7" cross CrossVersion.binary), - addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.2.4"), + ThisBuild / scalafmtOnCompile := true, + ThisBuild / scalacOptions -= "-Xplugin-require:macroparadise", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % catsV, - "org.typelevel" %% "kittens" % kittensV, - "org.typelevel" %% "alleycats-core" % catsV, - "org.typelevel" %% "mouse" % mouseV, - "org.typelevel" %% "cats-effect" % catsEffectV, - "org.technomadic" %% "turtles-core" % "0.1.0", - "org.apache.avro" % "avro" % "1.8.2", - - - "com.chuusai" %% "shapeless" % shapelessV, - "co.fs2" %% "fs2-core" % fs2V, - "co.fs2" %% "fs2-io" % fs2V, - "org.http4s" %% "http4s-dsl" % http4sV, - "org.http4s" %% "http4s-blaze-server" % http4sV, - "org.http4s" %% "http4s-blaze-client" % http4sV, - "org.http4s" %% "http4s-circe" % http4sV, - "io.circe" %% "circe-core" % circeV, - "io.circe" %% "circe-generic" % circeV, - "io.circe" %% "circe-parser" % circeV, - "org.tpolecat" %% "doobie-core" % doobieV, - "org.tpolecat" %% "doobie-h2" % doobieV, - "org.tpolecat" %% "doobie-hikari" % doobieV, - "org.tpolecat" %% "doobie-postgres" % doobieV, - "org.tpolecat" %% "doobie-specs2" % doobieV % Test, - "com.github.pureconfig" %% "pureconfig" % pureConfigV, - "eu.timepit" %% "refined" % refinedV, - "eu.timepit" %% "refined-scalacheck" % refinedV % Test, - "org.specs2" %% "specs2-core" % specs2V % Test, - "org.specs2" %% "specs2-scalacheck" % specs2V % Test, - "org.typelevel" %% "discipline" % disciplineV % Test, - "com.github.alexarchambault" %% "scalacheck-shapeless_1.13" % scShapelessV % Test + %%("cats-core"), + "org.technomadic" %% "turtles-core" % "0.1.0", + "org.apache.avro" % "avro" % "1.8.2", + %%("specs2-core") % Test, + %%("specs2-scalacheck") % Test, + "io.chrisdavenport" %% "cats-scalacheck" % "0.1.0" % Test + ), + orgProjectName := "Skeuomorph", + orgBadgeListSetting := List( + TravisBadge.apply, + CodecovBadge.apply, + { info => MavenCentralBadge.apply(info.copy(libName = "skeuomorph")) }, + ScalaLangBadge.apply, + LicenseBadge.apply, + { info => GitterBadge.apply(info.copy(owner = "frees-io", repo = "skeuomorph")) }, + GitHubIssuesBadge.apply ) -) +) ++ compilerPlugins -lazy val releaseSettings = { - import ReleaseTransformations._ - Seq( - releaseCrossBuild := true, - releaseProcess := Seq[ReleaseStep]( - checkSnapshotDependencies, - inquireVersions, - runClean, - runTest, - setReleaseVersion, - commitReleaseVersion, - tagRelease, - // For non cross-build projects, use releaseStepCommand("publishSigned") - releaseStepCommandAndRemaining("+publishSigned"), - setNextVersion, - commitNextVersion, - releaseStepCommand("sonatypeReleaseAll"), - pushChanges - ), - 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") - }, - credentials ++= ( - for { - username <- Option(System.getenv().get("SONATYPE_USERNAME")) - password <- Option(System.getenv().get("SONATYPE_PASSWORD")) - } yield - Credentials( - "Sonatype Nexus Repository Manager", - "oss.sonatype.org", - username, - password - ) - ).toSeq, - publishArtifact in Test := false, - releasePublishArtifactsAction := PgpKeys.publishSigned.value, - scmInfo := Some( - ScmInfo( - url("https://github.com/pepegar/skeuomorph"), - "git@github.com:pepegar/skeuomorph.git" - ) - ), - homepage := Some(url("https://github.com/pepegar/skeuomorph")), - licenses += ("MIT", url("http://opensource.org/licenses/MIT")), - publishMavenStyle := true, - pomIncludeRepository := { _ => - false - }, - pomExtra := { - - {for ((username, name) <- contributors) yield - - {username} - {name} - http://github.com/{username} - - } - - } - ) -} - -lazy val mimaSettings = { - import sbtrelease.Version - - def semverBinCompatVersions(major: Int, minor: Int, patch: Int): Set[(Int, Int, Int)] = { - val majorVersions: List[Int] = List(major) - val minorVersions: List[Int] = - if (major >= 1) Range(0, minor).inclusive.toList - else List(minor) - def patchVersions(currentMinVersion: Int): List[Int] = - if (minor == 0 && patch == 0) List.empty[Int] - else if (currentMinVersion != minor) List(0) - else Range(0, patch - 1).inclusive.toList - - val versions = for { - maj <- majorVersions - min <- minorVersions - pat <- patchVersions(min) - } yield (maj, min, pat) - versions.toSet - } - - def mimaVersions(version: String): Set[String] = { - Version(version) match { - case Some(Version(major, Seq(minor, patch), _)) => - semverBinCompatVersions(major.toInt, minor.toInt, patch.toInt) - .map { case (maj, min, pat) => maj.toString + "." + min.toString + "." + pat.toString } - case _ => - Set.empty[String] - } - } - // Safety Net For Exclusions - lazy val excludedVersions: Set[String] = Set() - - // Safety Net for Inclusions - lazy val extraVersions: Set[String] = Set() - - Seq( - mimaFailOnProblem := mimaVersions(version.value).toList.headOption.isDefined, - mimaPreviousArtifacts := (mimaVersions(version.value) ++ extraVersions) - .filterNot(excludedVersions.contains(_)) - .map { v => - val moduleN = moduleName.value + "_" + scalaBinaryVersion.value.toString - organization.value % moduleN % v - }, - mimaBinaryIssueFilters ++= { - import com.typesafe.tools.mima.core._ - import com.typesafe.tools.mima.core.ProblemFilters._ - Seq() - } +lazy val compilerPlugins = Seq( + libraryDependencies ++= Seq( + compilerPlugin("org.spire-math" % "kind-projector" % "0.9.7" cross CrossVersion.binary), + compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.2.4"), + compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.patch) ) -} +) -lazy val skipOnPublishSettings = Seq( +lazy val noPublishSettings = Seq( skip in publish := true, publish := (()), publishLocal := (()), diff --git a/docs/src/main/tut/optimizations.md b/docs/src/main/tut/optimizations.md new file mode 100644 index 00000000..78858b1f --- /dev/null +++ b/docs/src/main/tut/optimizations.md @@ -0,0 +1,73 @@ +--- +layout: page +title: Optimizations +position: 2 +--- + +# Optimizations + +The technique we use to model recursive data throughout `skeuomorph` +is called recursion schemes. Recursion schemes allows us to model our +data as non recursive and substitute direct recursion by a call to a +type parameter in the declaration. + +One of the techniques that we use from recursion schemes is +microoptimizations. We're able to transform ASTs by just describing +the optimization we want as a function, and the library provides +mechanisms to apply that function to the AST correctly. Let's see +`namedTypes` as an example: + +## NamedTypes + +```tut:invisible +import turtles._ +import turtles.data._ +import turtles.implicits._ +import skeuomorph.freestyle._ +import skeuomorph.freestyle.Schema._ +``` + +We found that when we wanted to render a schema to its string +representation and the schema had nested product types, the rendering +was not correct because it was printing the definition of the product +everywhere: + +``` +case class Product(field1: String, field2: case class OtherField()) + ^---------------------^ +// see how this is not valid scala code, it should be: + +case class Product(field1: String, field2: OtherField) +``` + +We solve this by substituting nested product types by it's name when +they're inside a product themselves. And we do this with the +`namedTypes` combinator: + +```tut:silent +def namedTypes[T](t: T)(implicit T: Birecursive.Aux[T, Schema]): T = + t.project match { + case TProduct(name, fields) => + TProduct[T]( + name, + fields.map { f: Field[T] => + f.copy(tpe = f.tpe.transCataT(_.project match { + case TProduct(name, _) => TNamedType[T](name).embed + case TSum(name, _) => TNamedType[T](name).embed + case other => other.embed + })) + } + ).embed + case other => other.embed + } +``` + +and then apply the `namedTypes` combinator to the AST: + +```tut:invisible +def ast[T](implicit T: Birecursive.Aux[T, Schema]): T = TNull[T]().embed +``` + +```tut +ast[Mu[Schema]].transCataT(namedTypes) +``` diff --git a/docs/src/main/tut/rendering.md b/docs/src/main/tut/rendering.md new file mode 100644 index 00000000..4f597bdd --- /dev/null +++ b/docs/src/main/tut/rendering.md @@ -0,0 +1,14 @@ +--- +layout: page +title: Rendering +position: 3 +--- + +# Rendering + +We're able to render schema representation to its String +representation. This library doesn't do Codegen, but you can check +out [freestyle-rpc][] idlGen plugin, which is based in skeuomorph. + + +[freestyle-rpc]: http://frees.io/docs/rpc/idl-generation diff --git a/docs/src/main/tut/schemas.md b/docs/src/main/tut/schemas.md new file mode 100644 index 00000000..93c6b8f8 --- /dev/null +++ b/docs/src/main/tut/schemas.md @@ -0,0 +1,36 @@ +--- +layout: page +title: Schemas +position: 1 +--- + +# Schemas + +> Examples in this page expects you to have the following imports in +> place: + +```tut:silent +import turtles.data._ +import turtles.implicits._ +import skeuomorph._ +``` + +Currently in skeuomorph there are schemas defined for different cases: + +- [Avro][] +- [Protobuf][] +- [freestyle-rpc][] + +## Schema conversions + + +| from\to | **Avro** | **Protobuf** | **freestyle-rpc** | +|-------------------|----------------------------------|----------------------------------|------------------------------------| +| **Avro** | | | `avro.transCata(fromAvro)` | +| **Protobuf** | | | `protobuf.transCata(fromProtobuf)` | +| **freestyle-rpc** | `frees.transCata(fromFreestyle)` | `frees.transCata(fromFreestyle)` | | + + +[Avro]: https://avro.apache.org/ +[Protobuf]: https://developers.google.com/protocol-buffers/ +[freestyle-rpc]: http://frees.io/docs/rpc/quickstart diff --git a/project/plugins.sbt b/project/plugins.sbt index cdb65ef0..1392d892 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -12,3 +12,4 @@ addSbtPlugin("com.47deg" % "sbt-microsites" % "0.7.16") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.2") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.0") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.3.4") +addSbtPlugin("io.frees" % "sbt-freestyle" % "0.3.23") diff --git a/src/main/scala/App.scala b/src/main/scala/App.scala index 0034a5af..296fcf92 100644 --- a/src/main/scala/App.scala +++ b/src/main/scala/App.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2018 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 skeuomorph object App {} diff --git a/src/main/scala/avro/schema.scala b/src/main/scala/avro/schema.scala index 87b4e114..0d0ba836 100644 --- a/src/main/scala/avro/schema.scala +++ b/src/main/scala/avro/schema.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2018 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 skeuomorph package avro diff --git a/src/main/scala/avro/util.scala b/src/main/scala/avro/util.scala index 0251717c..ef2ae22e 100644 --- a/src/main/scala/avro/util.scala +++ b/src/main/scala/avro/util.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2018 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 skeuomorph package avro diff --git a/src/main/scala/freestyle/Service.scala b/src/main/scala/freestyle/Service.scala index 2ef5dbaa..bc4ccd2b 100644 --- a/src/main/scala/freestyle/Service.scala +++ b/src/main/scala/freestyle/Service.scala @@ -1,29 +1,57 @@ +/* + * Copyright 2018 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 skeuomorph package freestyle import turtles._ import turtles.implicits._ -// We need to introduce an optimization here. Transform request & response types of operations into +/** + * [[Service]] describes a service representation in freestyle-rpc. + * + * @see http://frees.io/docs/rpc/idl-generation + */ case class Service[T](pkg: String, name: String, declarations: List[T], operations: List[Service.Operation[T]]) object Service { - import util._ - + /** + * Each one of the endpoints of the service + */ case class Operation[T](name: String, request: T, response: T) + /** + * Optimization to kill recursion in nested product/sum types while + * rendering. + */ def namedTypes[T](t: T)(implicit T: Birecursive.Aux[T, Schema]): T = t.project match { case Schema.TProduct(name, _) => Schema.TNamedType[T](name).embed case Schema.TSum(name, _) => Schema.TNamedType[T](name).embed case other => other.embed } - def renderService[T](service: Service[T])(implicit T: Birecursive.Aux[T, Schema]): String = { - val printDeclarations = service.declarations.map(_.cata(render)).mkString("\n") + /** + * Render a [[Service]] to its String representation + */ + def render[T](service: Service[T])(implicit T: Birecursive.Aux[T, Schema]): String = { + val printDeclarations = service.declarations.map(_.cata(util.render)).mkString("\n") val printOperations = service.operations.map { op => - val printRequest = op.request.transCataT(namedTypes).cata(render) - val printResponse = op.response.transCataT(namedTypes).cata(render) + val printRequest = op.request.transCataT(namedTypes).cata(util.render) + val printResponse = op.response.transCataT(namedTypes).cata(util.render) s"def ${op.name}(req: $printRequest): F[$printResponse]" } mkString ("\n ") diff --git a/src/main/scala/freestyle/schema.scala b/src/main/scala/freestyle/schema.scala index d04bb1ca..fa92efc2 100644 --- a/src/main/scala/freestyle/schema.scala +++ b/src/main/scala/freestyle/schema.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2018 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 skeuomorph package freestyle diff --git a/src/main/scala/freestyle/util.scala b/src/main/scala/freestyle/util.scala index 679fb6ed..4e8e8bcf 100644 --- a/src/main/scala/freestyle/util.scala +++ b/src/main/scala/freestyle/util.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2018 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 skeuomorph package freestyle diff --git a/src/main/scala/protobuf/schema.scala b/src/main/scala/protobuf/schema.scala index 8c7e05d0..5dad9a5c 100644 --- a/src/main/scala/protobuf/schema.scala +++ b/src/main/scala/protobuf/schema.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2018 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 skeuomorph package protobuf diff --git a/src/main/scala/protobuf/util.scala b/src/main/scala/protobuf/util.scala index 9c234ac7..5d7ed17f 100644 --- a/src/main/scala/protobuf/util.scala +++ b/src/main/scala/protobuf/util.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2018 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 skeuomorph package protobuf diff --git a/src/test/scala/avro/AvroSpec.scala b/src/test/scala/avro/AvroSpec.scala new file mode 100644 index 00000000..62d0f311 --- /dev/null +++ b/src/test/scala/avro/AvroSpec.scala @@ -0,0 +1,74 @@ +/* + * Copyright 2018 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 skeuomorph +package avro + +import scala.collection.JavaConverters._ + +import org.specs2._ +import org.scalacheck._ +import org.apache.avro.{Schema => AvroSchema} + +import turtles.Algebra +import turtles.data.Mu +import turtles.implicits._ + +class AvroSpec extends Specification with ScalaCheck { + + import instances._ + + def is = s2""" + Avro Schema + + It should be possible to create a Schema from org.apache.avro.Schema. $convertSchema + + It should be possible to create a Protocol from org.apache.avro.Protocol. $convertProtocol + """ + + def convertSchema = Prop.forAll { (schema: AvroSchema) => + schema + .ana[Mu[Schema]](util.fromAvro) + .cata(checkSchema(schema)) + } + + def convertProtocol = todo + + def checkSchema(sch: AvroSchema): Algebra[Schema, Boolean] = { + case Schema.TNull() => sch.getType should_== AvroSchema.Type.NULL + case Schema.TBoolean() => sch.getType should_== AvroSchema.Type.BOOLEAN + case Schema.TInt() => sch.getType should_== AvroSchema.Type.INT + case Schema.TLong() => sch.getType should_== AvroSchema.Type.LONG + case Schema.TFloat() => sch.getType should_== AvroSchema.Type.FLOAT + case Schema.TDouble() => sch.getType should_== AvroSchema.Type.DOUBLE + case Schema.TBytes() => sch.getType should_== AvroSchema.Type.BYTES + case Schema.TString() => sch.getType should_== AvroSchema.Type.STRING + + case Schema.TNamedType(_) => false + case Schema.TArray(_) => sch.getType should_== AvroSchema.Type.ARRAY + case Schema.TMap(_) => sch.getType should_== AvroSchema.Type.MAP + case Schema.TRecord(name, namespace, _, doc, fields) => + (sch.getName should_== name) + .and(sch.getNamespace should_== namespace.getOrElse("")) + .and(sch.getDoc should_== doc.getOrElse("")) + .and(sch.getFields.asScala.toList.map(f => (f.name, f.doc)) should_== fields.map(f => + (f.name, f.doc.getOrElse("")))) + + case Schema.TEnum(_, _, _, _, _) => true + case Schema.TUnion(_) => true + case Schema.TFixed(_, _, _, _) => true + } +} diff --git a/src/test/scala/instances.scala b/src/test/scala/instances.scala new file mode 100644 index 00000000..1d32a56f --- /dev/null +++ b/src/test/scala/instances.scala @@ -0,0 +1,73 @@ +/* + * Copyright 2018 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 skeuomorph +package avro + +import scala.collection.JavaConverters._ +import org.apache.avro.{Schema => AvroSchema} +import org.apache.avro.Schema.{Type => AvroType} + +import cats.implicits._ +import org.scalacheck._ +import org.scalacheck.cats.implicits._ + +object instances { + + implicit val avroSchemaArbitrary: Arbitrary[AvroSchema] = Arbitrary { + val primitives: Gen[AvroSchema] = Gen.oneOf( + List( + AvroType.STRING, + AvroType.BOOLEAN, + AvroType.BYTES, + AvroType.DOUBLE, + AvroType.FLOAT, + AvroType.INT, + AvroType.LONG, + AvroType.NULL + ).map(AvroSchema.create) + ) + + val nonEmptyString: Gen[String] = Gen.alphaStr.filter(_.nonEmpty) + + val arrayOrMap: Gen[AvroSchema] = + Gen.oneOf(primitives.map(AvroSchema.createMap), primitives.map(AvroSchema.createArray)) + + val union: Gen[AvroSchema] = + Gen.nonEmptyContainerOf[Set, AvroSchema](primitives).map(l => AvroSchema.createUnion(l.toList.asJava)) + + def field(name: String): Gen[AvroSchema.Field] = + for { + schema <- Gen.oneOf(primitives, arrayOrMap, union) + doc <- nonEmptyString + } yield new AvroSchema.Field(name, schema, doc, null.asInstanceOf[Any]) + + val record: Gen[AvroSchema] = ( + nonEmptyString, + nonEmptyString, + nonEmptyString, + Gen.nonEmptyContainerOf[Set, String](nonEmptyString).map(_.toList) flatMap { l: List[String] => + l.traverse(field) + } + ).mapN { + case (name, doc, namespace, fields) => + AvroSchema.createRecord(name, doc, namespace, false, fields.asJava) + } + + Gen.oneOf(primitives, arrayOrMap, union, record) + } + +}