├── .gitignore ├── .scalafmt.conf ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── cats └── src │ └── main │ └── scala │ └── com │ └── github │ └── mvv │ └── routineer │ └── cats │ └── package.scala ├── core └── src │ ├── main │ └── scala │ │ └── com │ │ └── github │ │ └── mvv │ │ └── routineer │ │ ├── Args.scala │ │ ├── Dispatch.scala │ │ ├── ParamValues.scala │ │ ├── PathParser.scala │ │ ├── QueryParser.scala │ │ ├── Route.scala │ │ ├── RoutePattern.scala │ │ ├── Routes.scala │ │ ├── ValueCheck.scala │ │ ├── ValuePattern.scala │ │ └── syntax │ │ └── package.scala │ └── test │ └── scala │ └── com │ └── github │ └── mvv │ └── routineer │ └── tests │ ├── AppendSpec.scala │ └── RoutesSpec.scala ├── examples └── servlet │ └── src │ └── main │ ├── scala │ └── com │ │ └── github │ │ └── mvv │ │ └── routineer │ │ └── examples │ │ └── servlet │ │ └── ExampleServlet.scala │ └── webapp │ └── WEB-INF │ └── web.xml └── project ├── build.properties ├── plugins.sbt └── secrets.tar.enc /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.swp 3 | *~ 4 | target 5 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 2.1.0 2 | maxColumn = 120 3 | align = most 4 | align.openParenDefnSite = true 5 | align.openParenCallSite = true 6 | includeCurlyBraceInSelectChains = false 7 | rewrite.rules = [SortImports, SortModifiers, RedundantBraces] 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | scala: 4 | - 2.13.1 5 | 6 | jdk: 7 | - openjdk8 8 | 9 | sudo: false 10 | 11 | cache: 12 | directories: 13 | - $HOME/.cache/coursier 14 | - $HOME/.ivy2/cache 15 | - $HOME/.sbt 16 | 17 | before_install: 18 | - "test -z \"$encrypted_027c1aa97902_key\" || (openssl aes-256-cbc -K $encrypted_027c1aa97902_key -iv $encrypted_027c1aa97902_iv -in project/secrets.tar.enc -out project/secrets.tar -d && tar -xf project/secrets.tar -C project)" 19 | 20 | stages: 21 | - name: lint 22 | - name: test 23 | - name: publish 24 | if: branch = master AND type = push 25 | 26 | jobs: 27 | include: 28 | - stage: lint 29 | name: "Check source code formatting" 30 | script: sbt ++$TRAVIS_SCALA_VERSION scalafmtCheck test:scalafmtCheck scalafmtSbtCheck 31 | - &test 32 | stage: test 33 | name: "Build and test for Scala 2.13" 34 | before_script: gpg --import project/ci.asc 35 | script: sbt ++$TRAVIS_SCALA_VERSION test package packageSrc publishLocal 36 | - <<: *test 37 | scala: 2.11.12 38 | name: "Build and test for Scala 2.11" 39 | - <<: *test 40 | scala: 2.12.10 41 | name: "Build and test for Scala 2.12" 42 | - &publish 43 | stage: publish 44 | name: "Publish" 45 | before_script: gpg --import project/ci.asc 46 | script: sbt +publish sonatypeBundleRelease 47 | 48 | env: 49 | global: 50 | - secure: "YPsDlswl38JbJnvEp6R7RsLrUHfDRLRKPsgz7woYu6qPqzTrasAqHebaz8EqXcLvJhT/IK/kB/O2yFunK09DkjGpW/2pZb96WgFzLjGAfFH6I8FfJ6etn+WZRn8GqUTA1hJGZgF5fbS36S2D0zG++vSlRyprdzbowCFMHJgo8BEn+iAdPYP/LoofLYQy9if2ihTnfPsC6IhJUAZssB5jKkmbdPtT2dSG1IqAFy605O/1y6WAtALT3sC8XsvwFq4JUiqNege0T9DGhKce08JAu3yQGUDnVjjsy7fEEXKxtuqAAb/znZ28+sZAR0CJ4lROkrJ2kjKCjxDJfMgPOdSLC2MtSpDU0X+janiBs6lWu7S059HCwWko/EKm7QsxqU52QG5M/eIpRGAzeUf+fclR2MTrioO2thfbeKnBmmPnBuwYYTnhfYG//eVKVpRn6z/rveCRmKNUvQkm1HIN+FoxcQeTgZ+9Gs19uyQxRSbz1MbVQu7noOBXjH9ihAFkhUwYg6cjNRLJB4Go/tCV8+brydBOTqQEYPrTxQBpMN35upqYQpV6lNn6L/cOmccNEkIlhJwUeMleF5XBZAoXwFFoB74UHnuHs2irTip21J2HhP77LXK6EiwTI8vgAT9N6oHN0FWBLBZIV1/LEXyQ0sLXwvLf0SxiZFPhh7yUZmKKCDo=" 51 | - secure: "J/sC3B+Xwl/d2onBq5u+p351B/vc0zoI66TKtTJyFll07dmqxdisUGaIEqw28GIaBxUk1XnDqTfl8r18+UpJk7QgwAY5oY8YVE4AZ6/4dVkTV55lznXwUfOROvXnr0s+x2HcDoRJ9Wq7HubO8t7JQkXSvRUxas1esDOzAjCr0AtgYMbSkyUlxJYhUdc0PJNGJVcyNzC+cgoPsML8gvN1eYhTQquwM7kXQIp0Exspb4MFidApxzcg/TpadnOBt2b0EclsybTrc5u5umrqTqx7zQedIdONwuatGJyNfHyVOzG9cKjtDLcwZvHrsRiyWKyfVNP0QdTyFjsPBtKtl09t6JwsOjaRn8IRkqHpvuqZHmiN1DpMgZ+4Bu7oid7d0Kn1tgW9WoC2OmRBGLfMGEZCee4krz3hPgY2HkrIW4BhFLoEV71yaISIpATm5E2uiMk9drJr1ZmfxEeVMVRcxFyfyljvp9NEBikpwKRt5xS8YpSYx3IoYnwE2Fvg6wMnmAF2saqZZrM12gqb/Ml4OzQtw0fuIxKfVZZdq6Wc+3yplaqJlDV4IqGPEDE6Sw49eb1Vzk6M7EJ+McdaGNKTllLQtR2gxvPPzcBRbgHQtc6G5Tab6SFxjzsAs4whWGS7G58SHbLZNavrgAuGKbv5vTqrKEGdR9wcUGNsijjxkKyytA8=" 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Routineer 2 | ========= 3 | Routineer is a Scala library that provides an internal DSL for declaring HTTP 4 | routes. Routes are constructed from strings and patterns and the type of the 5 | handling function is automatically inferred: 6 | 7 | get("prefix" /> * /> "middle" /> (IntP >>> PositiveP[Int])) { 8 | (req // Your request type, 9 | str // Inferred to be a String, 10 | i // Inferred to be an Int) => 11 | ... 12 | } 13 | 14 | Installation 15 | ------------ 16 | ### Automatic 17 | Routineer artifacts `routineer` and `routineer-scalaz` are published at 18 | [Scala-Tools.org repository](http://nexus.scala-tools.org) with groupId 19 | `com.github.mvv.routineer`. Add the following lines to your `pom.xml` if you 20 | are using Maven: 21 | 22 | 23 | com.github.mvv.routineer 24 | routineer_YOUR-SCALA-VERSION 25 | 0.1.2 26 | 27 | 28 | Or add this line to your build file if you are using Simple Build Tool: 29 | 30 | libraryDependencies += "com.github.mvv.routineer" %% "routineer" % "0.1.2" 31 | 32 | ### From source 33 | Install [Simple Build Tool](https://github.com/harrah/xsbt), run 34 | 35 | $ sbt update publish-local publish-local-maven 36 | 37 | If you plan to use Routineer with [Scalaz](http://code.google.com/p/scalaz), 38 | run 39 | 40 | $ sbt "project routineer-scalaz" update publish-local publish-local-maven 41 | 42 | Usage 43 | ----- 44 | There is no documentation at the moment, look at the 45 | [example](https://github.com/mvv/routineer/blob/master/examples/servlet/src/ExampleServlet.scala) 46 | 47 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import xerial.sbt.Sonatype._ 4 | 5 | inThisBuild( 6 | Seq( 7 | organization := "com.github.mvv.routineer", 8 | version := "0.2-M2", 9 | homepage := Some(url("https://github.com/mvv/routineer")), 10 | scmInfo := Some(ScmInfo(url("https://github.com/mvv/routineer"), "scm:git@github.com:mvv/routineer.git")), 11 | licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), 12 | developers := List( 13 | Developer(id = "mvv", 14 | name = "Mikhail Vorozhtsov", 15 | email = "mikhail.vorozhtsov@gmail.com", 16 | url = url("https://github.com/mvv")) 17 | ), 18 | sonatypeProjectHosting := Some(GitHubHosting("mvv", "routineer", "mikhail.vorozhtsov@gmail.com")) 19 | ) 20 | ) 21 | 22 | ThisBuild / publishTo := sonatypePublishToBundle.value 23 | ThisBuild / publishMavenStyle := true 24 | 25 | inThisBuild( 26 | Seq( 27 | crossScalaVersions := Seq("2.13.1", "2.12.10", "2.11.12"), 28 | scalaVersion := crossScalaVersions.value.head, 29 | scalacOptions ++= Seq("-feature", "-deprecation", "-unchecked", "-Xfatal-warnings") 30 | ) 31 | ) 32 | 33 | lazy val root = (project in file(".")) 34 | .settings( 35 | crossScalaVersions := Nil, 36 | skip in publish := true, 37 | sonatypeProfileName := "com.github.mvv", 38 | sonatypeSessionName := s"Routineer_${version.value}" 39 | ) 40 | .aggregate(core, cats, examples) 41 | 42 | val specs2 = "org.specs2" %% "specs2-core" % "4.7.1" % Test 43 | 44 | lazy val core = (project in file("core")) 45 | .settings(name := "routineer", libraryDependencies += specs2) 46 | 47 | lazy val cats = (project in file("cats")) 48 | .settings( 49 | name := "routineer-cats", 50 | libraryDependencies ++= 51 | Seq("org.typelevel" %% "cats-core" % "2.0.0" % Provided, specs2) 52 | ) 53 | .dependsOn(core) 54 | 55 | lazy val examples = (project in file("examples")) 56 | .settings( 57 | name := "routineer-examples", 58 | crossScalaVersions := Nil, 59 | skip in publish := true 60 | ) 61 | .aggregate(examplesServlet) 62 | 63 | lazy val examplesServlet = (project in file("examples/servlet")) 64 | .enablePlugins(JettyPlugin) 65 | .settings(name := "routineer-examples-servlet", 66 | skip in publish := true, 67 | libraryDependencies += 68 | "javax.servlet" % "javax.servlet-api" % "3.0.1" % Provided) 69 | .dependsOn(core) 70 | -------------------------------------------------------------------------------- /cats/src/main/scala/com/github/mvv/routineer/cats/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011-2013 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer 18 | 19 | import _root_.cats.Monoid 20 | import _root_.cats.arrow.Arrow 21 | import _root_.cats.data.{NonEmptyList, NonEmptySet} 22 | 23 | import scala.collection.immutable.SortedSet 24 | import scala.language.higherKinds 25 | 26 | package object cats { 27 | implicit def routineerRoutesMonoid[H[_ <: Args]]: Monoid[Routes[H]] = 28 | new Monoid[Routes[H]] { 29 | override def empty: Routes[H] = Routes.empty 30 | override def combine(rs1: Routes[H], rs2: Routes[H]): Routes[H] = rs1 ++ rs2 31 | } 32 | implicit object routineerValuePatternArrow extends Arrow[ValuePattern] { 33 | override def id[A]: ValuePattern[A, A] = ValuePattern.id[A] 34 | override def lift[A, B](f: A => B): ValuePattern[A, B] = ValuePattern.map(f) 35 | override def compose[A, B, C](f: ValuePattern[B, C], g: ValuePattern[A, B]): ValuePattern[A, C] = g >>> f 36 | override def first[A, B, C](pattern: ValuePattern[A, B]): ValuePattern[(A, C), (B, C)] = pattern *** id[C] 37 | override def second[A, B, C](pattern: ValuePattern[A, B]): ValuePattern[(C, A), (C, B)] = id[C] *** pattern 38 | } 39 | 40 | object NonEmptyListP extends ValuePattern[Seq[String], NonEmptyList[String]] { 41 | override def apply(in: Seq[String]): ValuePattern.Match[NonEmptyList[String]] = 42 | NonEmptyList.fromList(in.toList) match { 43 | case Some(result) => ValuePattern.Matched(result) 44 | case None => ValuePattern.NotMatched("No value") 45 | } 46 | } 47 | 48 | object NonEmptySetP extends ValuePattern[Seq[String], NonEmptySet[String]] { 49 | override def apply(in: Seq[String]): ValuePattern.Match[NonEmptySet[String]] = 50 | NonEmptySet.fromSet(SortedSet(in: _*)) match { 51 | case Some(result) => ValuePattern.Matched(result) 52 | case None => ValuePattern.NotMatched("No value") 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/src/main/scala/com/github/mvv/routineer/Args.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer 18 | 19 | import scala.language.higherKinds 20 | 21 | sealed trait Args { 22 | type Fn[+_] 23 | def through[R](f: Fn[R]): R 24 | 25 | type Prepend[H] <: Args 26 | type Append[L] <: Args 27 | } 28 | 29 | object Args { 30 | sealed trait Growable extends Args { 31 | def prepend[H](head: H): Prepend[H] 32 | def append[L](last: L): Append[L] 33 | } 34 | 35 | sealed trait Same[A <: Args, B <: Args] { 36 | def subst[F[_ <: Args]](f: F[A]): F[B] 37 | def symm: Same[B, A] 38 | } 39 | 40 | def same[A <: Args]: Same[A, A] = new Same[A, A] { 41 | override def subst[F[_ <: Args]](f: F[A]): F[A] = f 42 | override def symm: Same[A, A] = this 43 | } 44 | 45 | def apply[A <: Args, R](f: A#Fn[R], args: A): R = args.through(f.asInstanceOf[args.Fn[R]]) 46 | 47 | final class _0 extends Growable { 48 | override type Fn[+R] = () => R 49 | override def through[R](f: Fn[R]): R = f() 50 | 51 | override type Prepend[H] = _1[H] 52 | override type Append[L] = _1[L] 53 | override def prepend[H](head: H): Prepend[H] = new _1(head) 54 | override def append[H](last: H): Append[H] = new _1(last) 55 | } 56 | val _0: _0 = new _0 57 | final class _1[A] private[Args] (_1: A) extends Growable { 58 | override type Fn[+R] = A => R 59 | override def through[R](f: Fn[R]): R = f(_1) 60 | 61 | override type Prepend[H] = _2[H, A] 62 | override type Append[L] = _2[A, L] 63 | override def prepend[H](head: H): Prepend[H] = new _2(head, _1) 64 | override def append[L](last: L): Append[L] = new _2(_1, last) 65 | } 66 | final class _2[A1, A2] private[Args] (_1: A1, _2: A2) extends Growable { 67 | override type Fn[+R] = (A1, A2) => R 68 | override def through[R](f: Fn[R]): R = f(_1, _2) 69 | 70 | override type Prepend[H] = _3[H, A1, A2] 71 | override type Append[L] = _3[A1, A2, L] 72 | override def prepend[H](head: H): Prepend[H] = new _3(head, _1, _2) 73 | override def append[L](last: L): Append[L] = new _3(_1, _2, last) 74 | } 75 | final class _3[A1, A2, A3] private[Args] (_1: A1, _2: A2, _3: A3) extends Growable { 76 | override type Fn[+R] = (A1, A2, A3) => R 77 | override def through[R](f: Fn[R]): R = f(_1, _2, _3) 78 | 79 | override type Prepend[H] = _4[H, A1, A2, A3] 80 | override type Append[L] = _4[A1, A2, A3, L] 81 | override def prepend[H](head: H): Prepend[H] = new _4(head, _1, _2, _3) 82 | override def append[L](last: L): Append[L] = new _4(_1, _2, _3, last) 83 | } 84 | final class _4[A1, A2, A3, A4] private[Args] (_1: A1, _2: A2, _3: A3, _4: A4) extends Growable { 85 | override type Fn[+R] = (A1, A2, A3, A4) => R 86 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4) 87 | 88 | override type Prepend[H] = _5[H, A1, A2, A3, A4] 89 | override type Append[L] = _5[A1, A2, A3, A4, L] 90 | override def prepend[H](head: H): Prepend[H] = new _5(head, _1, _2, _3, _4) 91 | override def append[L](last: L): Append[L] = new _5(_1, _2, _3, _4, last) 92 | } 93 | final class _5[A1, A2, A3, A4, A5] private[Args] (_1: A1, _2: A2, _3: A3, _4: A4, _5: A5) extends Growable { 94 | override type Fn[+R] = (A1, A2, A3, A4, A5) => R 95 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5) 96 | 97 | override type Prepend[H] = _6[H, A1, A2, A3, A4, A5] 98 | override type Append[L] = _6[A1, A2, A3, A4, A5, L] 99 | override def prepend[H](head: H): Prepend[H] = new _6(head, _1, _2, _3, _4, _5) 100 | override def append[L](last: L): Append[L] = new _6(_1, _2, _3, _4, _5, last) 101 | } 102 | final class _6[A1, A2, A3, A4, A5, A6] private[Args] (_1: A1, _2: A2, _3: A3, _4: A4, _5: A5, _6: A6) 103 | extends Growable { 104 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6) => R 105 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6) 106 | 107 | override type Prepend[H] = _7[H, A1, A2, A3, A4, A5, A6] 108 | override type Append[L] = _7[A1, A2, A3, A4, A5, A6, L] 109 | override def prepend[H](head: H): Prepend[H] = new _7(head, _1, _2, _3, _4, _5, _6) 110 | override def append[L](last: L): Append[L] = new _7(_1, _2, _3, _4, _5, _6, last) 111 | } 112 | final class _7[A1, A2, A3, A4, A5, A6, A7] private[Args] (_1: A1, _2: A2, _3: A3, _4: A4, _5: A5, _6: A6, _7: A7) 113 | extends Growable { 114 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7) => R 115 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6, _7) 116 | 117 | override type Prepend[H] = _8[H, A1, A2, A3, A4, A5, A6, A7] 118 | override type Append[L] = _8[A1, A2, A3, A4, A5, A6, A7, L] 119 | override def prepend[H](head: H): Prepend[H] = new _8(head, _1, _2, _3, _4, _5, _6, _7) 120 | override def append[L](last: L): Append[L] = new _8(_1, _2, _3, _4, _5, _6, _7, last) 121 | } 122 | final class _8[A1, A2, A3, A4, A5, A6, A7, A8] private[Args] (_1: A1, 123 | _2: A2, 124 | _3: A3, 125 | _4: A4, 126 | _5: A5, 127 | _6: A6, 128 | _7: A7, 129 | _8: A8) 130 | extends Growable { 131 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7, A8) => R 132 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6, _7, _8) 133 | 134 | override type Prepend[H] = _9[H, A1, A2, A3, A4, A5, A6, A7, A8] 135 | override type Append[L] = _9[A1, A2, A3, A4, A5, A6, A7, A8, L] 136 | override def prepend[H](head: H): Prepend[H] = new _9(head, _1, _2, _3, _4, _5, _6, _7, _8) 137 | override def append[L](last: L): Append[L] = new _9(_1, _2, _3, _4, _5, _6, _7, _8, last) 138 | } 139 | final class _9[A1, A2, A3, A4, A5, A6, A7, A8, A9] private[Args] (_1: A1, 140 | _2: A2, 141 | _3: A3, 142 | _4: A4, 143 | _5: A5, 144 | _6: A6, 145 | _7: A7, 146 | _8: A8, 147 | _9: A9) 148 | extends Growable { 149 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7, A8, A9) => R 150 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6, _7, _8, _9) 151 | 152 | override type Prepend[H] = _10[H, A1, A2, A3, A4, A5, A6, A7, A8, A9] 153 | override type Append[L] = _10[A1, A2, A3, A4, A5, A6, A7, A8, A9, L] 154 | override def prepend[H](head: H): Prepend[H] = new _10(head, _1, _2, _3, _4, _5, _6, _7, _8, _9) 155 | override def append[L](last: L): Append[L] = new _10(_1, _2, _3, _4, _5, _6, _7, _8, _9, last) 156 | } 157 | final class _10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10] private[Args] (_1: A1, 158 | _2: A2, 159 | _3: A3, 160 | _4: A4, 161 | _5: A5, 162 | _6: A6, 163 | _7: A7, 164 | _8: A8, 165 | _9: A9, 166 | _10: A10) 167 | extends Growable { 168 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) => R 169 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) 170 | 171 | override type Prepend[H] = _11[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10] 172 | override type Append[L] = _11[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, L] 173 | override def prepend[H](head: H): Prepend[H] = new _11(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) 174 | override def append[L](last: L): Append[L] = new _11(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, last) 175 | } 176 | final class _11[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11] private[Args] (_1: A1, 177 | _2: A2, 178 | _3: A3, 179 | _4: A4, 180 | _5: A5, 181 | _6: A6, 182 | _7: A7, 183 | _8: A8, 184 | _9: A9, 185 | _10: A10, 186 | _11: A11) 187 | extends Growable { 188 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11) => R 189 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) 190 | 191 | override type Prepend[H] = _12[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11] 192 | override type Append[L] = _12[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, L] 193 | override def prepend[H](head: H): Prepend[H] = new _12(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) 194 | override def append[L](last: L): Append[L] = new _12(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, last) 195 | } 196 | final class _12[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12] private[Args] (_1: A1, 197 | _2: A2, 198 | _3: A3, 199 | _4: A4, 200 | _5: A5, 201 | _6: A6, 202 | _7: A7, 203 | _8: A8, 204 | _9: A9, 205 | _10: A10, 206 | _11: A11, 207 | _12: A12) 208 | extends Growable { 209 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12) => R 210 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) 211 | 212 | override type Prepend[H] = _13[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12] 213 | override type Append[L] = _13[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, L] 214 | override def prepend[H](head: H): Prepend[H] = new _13(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) 215 | override def append[L](last: L): Append[L] = new _13(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, last) 216 | } 217 | final class _13[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13] private[Args] (_1: A1, 218 | _2: A2, 219 | _3: A3, 220 | _4: A4, 221 | _5: A5, 222 | _6: A6, 223 | _7: A7, 224 | _8: A8, 225 | _9: A9, 226 | _10: A10, 227 | _11: A11, 228 | _12: A12, 229 | _13: A13) 230 | extends Growable { 231 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13) => R 232 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) 233 | 234 | override type Prepend[H] = _14[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13] 235 | override type Append[L] = _14[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, L] 236 | override def prepend[H](head: H): Prepend[H] = new _14(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) 237 | override def append[L](last: L): Append[L] = new _14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, last) 238 | } 239 | final class _14[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14] private[Args] (_1: A1, 240 | _2: A2, 241 | _3: A3, 242 | _4: A4, 243 | _5: A5, 244 | _6: A6, 245 | _7: A7, 246 | _8: A8, 247 | _9: A9, 248 | _10: A10, 249 | _11: A11, 250 | _12: A12, 251 | _13: A13, 252 | _14: A14) 253 | extends Growable { 254 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14) => R 255 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) 256 | 257 | override type Prepend[H] = _15[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14] 258 | override type Append[L] = _15[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, L] 259 | override def prepend[H](head: H): Prepend[H] = 260 | new _15(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) 261 | override def append[L](last: L): Append[L] = 262 | new _15(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, last) 263 | } 264 | final class _15[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15] private[Args] (_1: A1, 265 | _2: A2, 266 | _3: A3, 267 | _4: A4, 268 | _5: A5, 269 | _6: A6, 270 | _7: A7, 271 | _8: A8, 272 | _9: A9, 273 | _10: A10, 274 | _11: A11, 275 | _12: A12, 276 | _13: A13, 277 | _14: A14, 278 | _15: A15) 279 | extends Growable { 280 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15) => R 281 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) 282 | 283 | override type Prepend[H] = _16[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15] 284 | override type Append[L] = _16[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, L] 285 | override def prepend[H](head: H): Prepend[H] = 286 | new _16(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) 287 | override def append[L](last: L): Append[L] = 288 | new _16(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, last) 289 | } 290 | final class _16[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16] private[Args] (_1: A1, 291 | _2: A2, 292 | _3: A3, 293 | _4: A4, 294 | _5: A5, 295 | _6: A6, 296 | _7: A7, 297 | _8: A8, 298 | _9: A9, 299 | _10: A10, 300 | _11: A11, 301 | _12: A12, 302 | _13: A13, 303 | _14: A14, 304 | _15: A15, 305 | _16: A16) 306 | extends Growable { 307 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16) => R 308 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) 309 | 310 | override type Prepend[H] = _17[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16] 311 | override type Append[L] = _17[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, L] 312 | override def prepend[H](head: H): Prepend[H] = 313 | new _17(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) 314 | override def append[L](last: L): Append[L] = 315 | new _17(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, last) 316 | } 317 | final class _17[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17] private[Args] (_1: A1, 318 | _2: A2, 319 | _3: A3, 320 | _4: A4, 321 | _5: A5, 322 | _6: A6, 323 | _7: A7, 324 | _8: A8, 325 | _9: A9, 326 | _10: A10, 327 | _11: A11, 328 | _12: A12, 329 | _13: A13, 330 | _14: A14, 331 | _15: A15, 332 | _16: A16, 333 | _17: A17) 334 | extends Growable { 335 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17) => R 336 | override def through[R](f: Fn[R]): R = f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) 337 | 338 | override type Prepend[H] = _18[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17] 339 | override type Append[L] = _18[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, L] 340 | override def prepend[H](head: H): Prepend[H] = 341 | new _18(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) 342 | override def append[L](last: L): Append[L] = 343 | new _18(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, last) 344 | } 345 | final class _18[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18] private[Args] ( 346 | _1: A1, 347 | _2: A2, 348 | _3: A3, 349 | _4: A4, 350 | _5: A5, 351 | _6: A6, 352 | _7: A7, 353 | _8: A8, 354 | _9: A9, 355 | _10: A10, 356 | _11: A11, 357 | _12: A12, 358 | _13: A13, 359 | _14: A14, 360 | _15: A15, 361 | _16: A16, 362 | _17: A17, 363 | _18: A18 364 | ) extends Growable { 365 | override type Fn[+R] = (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18) => R 366 | override def through[R](f: Fn[R]): R = 367 | f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) 368 | 369 | override type Prepend[H] = _19[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18] 370 | override type Append[L] = _19[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, L] 371 | override def prepend[H](head: H): Prepend[H] = 372 | new _19(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) 373 | override def append[L](last: L): Append[L] = 374 | new _19(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, last) 375 | } 376 | final class _19[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19] private[Args] ( 377 | _1: A1, 378 | _2: A2, 379 | _3: A3, 380 | _4: A4, 381 | _5: A5, 382 | _6: A6, 383 | _7: A7, 384 | _8: A8, 385 | _9: A9, 386 | _10: A10, 387 | _11: A11, 388 | _12: A12, 389 | _13: A13, 390 | _14: A14, 391 | _15: A15, 392 | _16: A16, 393 | _17: A17, 394 | _18: A18, 395 | _19: A19 396 | ) extends Growable { 397 | override type Fn[+R] = 398 | (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19) => R 399 | override def through[R](f: Fn[R]): R = 400 | f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) 401 | 402 | override type Prepend[H] = 403 | _20[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19] 404 | override type Append[L] = 405 | _20[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, L] 406 | override def prepend[H](head: H): Prepend[H] = 407 | new _20(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) 408 | override def append[L](last: L): Append[L] = 409 | new _20(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, last) 410 | } 411 | final class _20[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20] private[Args] ( 412 | _1: A1, 413 | _2: A2, 414 | _3: A3, 415 | _4: A4, 416 | _5: A5, 417 | _6: A6, 418 | _7: A7, 419 | _8: A8, 420 | _9: A9, 421 | _10: A10, 422 | _11: A11, 423 | _12: A12, 424 | _13: A13, 425 | _14: A14, 426 | _15: A15, 427 | _16: A16, 428 | _17: A17, 429 | _18: A18, 430 | _19: A19, 431 | _20: A20 432 | ) extends Growable { 433 | override type Fn[+R] = 434 | (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20) => R 435 | override def through[R](f: Fn[R]): R = 436 | f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20) 437 | 438 | override type Prepend[H] = 439 | _21[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20] 440 | override type Append[L] = 441 | _21[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, L] 442 | override def prepend[H](head: H): Prepend[H] = 443 | new _21(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20) 444 | override def append[L](last: L): Append[L] = 445 | new _21(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, last) 446 | } 447 | final class _21[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21] private[Args] ( 448 | _1: A1, 449 | _2: A2, 450 | _3: A3, 451 | _4: A4, 452 | _5: A5, 453 | _6: A6, 454 | _7: A7, 455 | _8: A8, 456 | _9: A9, 457 | _10: A10, 458 | _11: A11, 459 | _12: A12, 460 | _13: A13, 461 | _14: A14, 462 | _15: A15, 463 | _16: A16, 464 | _17: A17, 465 | _18: A18, 466 | _19: A19, 467 | _20: A20, 468 | _21: A21 469 | ) extends Growable { 470 | override type Fn[+R] = 471 | (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21) => R 472 | override def through[R](f: Fn[R]): R = 473 | f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) 474 | 475 | override type Prepend[H] = 476 | _22[H, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21] 477 | override type Append[L] = 478 | _22[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, L] 479 | override def prepend[H](head: H): Prepend[H] = 480 | new _22(head, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) 481 | override def append[L](last: L): Append[L] = 482 | new _22(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, last) 483 | } 484 | final class _22[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22] private[Args] ( 485 | _1: A1, 486 | _2: A2, 487 | _3: A3, 488 | _4: A4, 489 | _5: A5, 490 | _6: A6, 491 | _7: A7, 492 | _8: A8, 493 | _9: A9, 494 | _10: A10, 495 | _11: A11, 496 | _12: A12, 497 | _13: A13, 498 | _14: A14, 499 | _15: A15, 500 | _16: A16, 501 | _17: A17, 502 | _18: A18, 503 | _19: A19, 504 | _20: A20, 505 | _21: A21, 506 | _22: A22 507 | ) extends Args { 508 | override type Fn[+R] = 509 | (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22) => R 510 | override def through[R](f: Fn[R]): R = 511 | f(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22) 512 | 513 | override type Prepend[H] = 514 | _22[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22] 515 | override type Append[L] = 516 | _22[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22] 517 | } 518 | } 519 | -------------------------------------------------------------------------------- /core/src/main/scala/com/github/mvv/routineer/Dispatch.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer 18 | 19 | import scala.language.higherKinds 20 | 21 | sealed trait Dispatch[+H[_ <: Args]] 22 | 23 | object Dispatch { 24 | final case class Handler[O <: Args, +H[_ <: Args]](args: O, handler: H[O], next: () => Dispatch[H]) 25 | extends Dispatch[H] 26 | final case class Failure(context: Context, first: String, rest: String*) extends Dispatch[Route.NoHandler] { 27 | def toSeq: Seq[String] = first +: rest 28 | } 29 | case object NotFound extends Dispatch[Route.NoHandler] 30 | 31 | sealed trait Context 32 | final case class InSegment(prefix: Seq[String], segment: String) extends Context 33 | final case class InSegments(prefix: Seq[String], segments: Seq[String]) extends Context 34 | final case class InParam(prefix: Seq[String], name: String, values: Seq[String]) extends Context 35 | final case class InParams(prefix: Seq[String], params: Map[String, ParamValues]) extends Context 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/scala/com/github/mvv/routineer/ParamValues.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer 18 | 19 | final case class ParamValues(first: String, rest: String*) { 20 | def last: String = rest.lastOption.getOrElse(first) 21 | def toSeq: Seq[String] = first +: rest 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/scala/com/github/mvv/routineer/PathParser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer 18 | 19 | trait PathParser { 20 | def segment: PathParser.GetSegment 21 | } 22 | 23 | object PathParser { 24 | sealed trait GetSegment 25 | final case class Segment(segment: String, parser: PathParser) extends GetSegment 26 | final case class PathEnd(parser: QueryParser) extends GetSegment 27 | final case class Failure(error: String) extends GetSegment 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/scala/com/github/mvv/routineer/QueryParser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer 18 | 19 | trait QueryParser { 20 | def params: QueryParser.GetParams 21 | } 22 | 23 | object QueryParser { 24 | sealed trait GetParams 25 | final case class Params(params: Map[String, ParamValues]) extends GetParams 26 | final case class Failure(error: String) extends GetParams 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/scala/com/github/mvv/routineer/Route.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2019 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer 18 | 19 | import scala.language.higherKinds 20 | 21 | final case class Route[O <: Args, +H[_ <: Args]](pattern: RoutePattern[O], handler: H[O]) 22 | 23 | object Route { 24 | type NoHandler[_ <: Args] = Nothing 25 | 26 | type Handler[R, O <: Args] = O#Fn[R] 27 | object Handler { 28 | trait Apply[R] { 29 | type H[O <: Args] = Handler[R, O] 30 | } 31 | } 32 | def handle[R, O <: Args](pattern: RoutePattern[O])(handler: O#Fn[R]): Route[O, Handler.Apply[R]#H] = 33 | Route[O, Handler.Apply[R]#H](pattern, handler) 34 | 35 | final case class WithEnv[E, R, O <: Args](handler: O#Prepend[E]#Fn[R], growable: O <:< O with Args.Growable) 36 | object WithEnv { 37 | trait Apply[E, R] { 38 | type H[O <: Args] = WithEnv[E, R, O] 39 | } 40 | } 41 | 42 | final class withEnvFor[E] { 43 | def apply[R, O <: Args]( 44 | pattern: RoutePattern[O] 45 | )(handler: O#Prepend[E]#Fn[R])(implicit witness: O <:< O with Args.Growable): Route[O, WithEnv.Apply[E, R]#H] = 46 | Route[O, WithEnv.Apply[E, R]#H](pattern, WithEnv[E, R, O](handler, witness)) 47 | } 48 | def withEnv[E]: withEnvFor[E] = new withEnvFor[E] 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/scala/com/github/mvv/routineer/RoutePattern.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer 18 | 19 | import scala.language.higherKinds 20 | 21 | sealed trait RoutePattern[O <: Args] { 22 | def trace: RoutePattern.PathTrace[Args._0, O, RoutePattern.PathEnd] 23 | } 24 | 25 | object RoutePattern { 26 | sealed trait PathTrace[I <: Args, O <: Args, +E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]] 27 | final case class SegmentCheck[I <: Args, O <: Args, +E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 28 | check: ValueCheck[String], 29 | force: Boolean, 30 | next: PathTrace[I, O, E] 31 | ) extends PathTrace[I, O, E] 32 | final case class SegmentPattern[A, I <: Args, O <: Args, +E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 33 | pattern: ValuePattern[String, A], 34 | force: Boolean, 35 | next: PathTrace[I#Append[A], O, E], 36 | growable: I <:< I with Args.Growable 37 | ) extends PathTrace[I, O, E] 38 | final case class SegmentsCheck[I <: Args, O <: Args, +E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 39 | check: ValueCheck[Seq[String]], 40 | force: Boolean, 41 | next: E[I, O] 42 | ) extends PathTrace[I, O, E] 43 | final case class SegmentsPattern[A, I <: Args, O <: Args, +E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 44 | pattern: ValuePattern[Seq[String], A], 45 | force: Boolean, 46 | end: E[I#Append[A], O], 47 | growable: I <:< I with Args.Growable 48 | ) extends PathTrace[I, O, E] 49 | final case class PathMatched[I <: Args, O <: Args, +E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]](end: E[I, O]) 50 | extends PathTrace[I, O, E] 51 | 52 | sealed trait PathEnd[I <: Args, O <: Args] 53 | object PathEnd { 54 | final case class WithoutQuery[I <: Args, O <: Args](same: Args.Same[I, O]) extends PathEnd[I, O] 55 | final case class WithQuery[I <: Args, O <: Args](trace: QueryTrace[I, O]) extends PathEnd[I, O] 56 | } 57 | 58 | sealed trait QueryTrace[I <: Args, O <: Args] 59 | final case class ParamCheck[I <: Args, O <: Args](name: String, 60 | check: ValueCheck[Seq[String]], 61 | force: Boolean, 62 | next: QueryTrace[I, O]) 63 | extends QueryTrace[I, O] 64 | final case class ParamPattern[A, I <: Args, O <: Args](name: String, 65 | check: ValuePattern[Seq[String], A], 66 | force: Boolean, 67 | next: QueryTrace[I#Append[A], O], 68 | growable: I <:< I with Args.Growable) 69 | extends QueryTrace[I, O] 70 | final case class ParamsCheck[I <: Args, O <: Args](check: ValueCheck[Map[String, ParamValues]], 71 | force: Boolean, 72 | same: Args.Same[I, O]) 73 | extends QueryTrace[I, O] 74 | final case class ParamsPattern[A, I <: Args, O <: Args](pattern: ValuePattern[Map[String, ParamValues], A], 75 | force: Boolean, 76 | same: Args.Same[I#Append[A], O], 77 | growable: I <:< I with Args.Growable) 78 | extends QueryTrace[I, O] 79 | final case class QueryMatched[I <: Args, O <: Args](same: Args.Same[I, O]) extends QueryTrace[I, O] 80 | 81 | sealed trait OnlyPath[O <: Args] extends RoutePattern[O] { 82 | def ?>(name: String, check: ValueCheck[Seq[String]]): PathWithPartialQuery[O] 83 | def ?>(name: String, check: ValueCheck.Backtrack[Seq[String]]): PathWithPartialQuery[O] 84 | def ?>[A](name: String, pattern: ValuePattern[Seq[String], A])( 85 | implicit growable: O <:< O with Args.Growable 86 | ): PathWithPartialQuery[O#Append[A]] 87 | def ?>[A](name: String, pattern: ValuePattern.Backtrack[Seq[String], A])( 88 | implicit growable: O <:< O with Args.Growable 89 | ): PathWithPartialQuery[O#Append[A]] 90 | def ?+(check: ValueCheck[Map[String, ParamValues]]): PathWithFullQuery[O] 91 | def ?+(check: ValueCheck.Backtrack[Map[String, ParamValues]]): PathWithFullQuery[O] 92 | def ?+[A](check: ValuePattern[Map[String, ParamValues], A])( 93 | implicit growable: O <:< O with Args.Growable 94 | ): PathWithFullQuery[O#Append[A]] 95 | def ?+[A](check: ValuePattern.Backtrack[Map[String, ParamValues], A])( 96 | implicit growable: O <:< O with Args.Growable 97 | ): PathWithFullQuery[O#Append[A]] 98 | override def trace: RoutePattern.PathTrace[Args._0, O, PathEnd.WithoutQuery] 99 | } 100 | 101 | sealed trait PartialPath[O <: Args] extends OnlyPath[O] { 102 | def />(check: ValueCheck[String]): PartialPath[O] 103 | def />(check: ValueCheck.Force[String]): PartialPath[O] 104 | def />[A](pattern: ValuePattern[String, A])(implicit growable: O <:< O with Args.Growable): PartialPath[O#Append[A]] 105 | def />[A](pattern: ValuePattern.Force[String, A])( 106 | implicit growable: O <:< O with Args.Growable 107 | ): PartialPath[O#Append[A]] 108 | def /+(check: ValueCheck[Seq[String]]): FullPath[O] 109 | def /+(check: ValueCheck.Force[Seq[String]]): FullPath[O] 110 | def /+[A](check: ValuePattern[Seq[String], A])(implicit growable: O <:< O with Args.Growable): FullPath[O#Append[A]] 111 | def /+[A](check: ValuePattern.Force[Seq[String], A])( 112 | implicit growable: O <:< O with Args.Growable 113 | ): FullPath[O#Append[A]] 114 | } 115 | 116 | sealed trait FullPath[O <: Args] extends OnlyPath[O] 117 | 118 | sealed trait PathWithPartialQuery[O <: Args] extends RoutePattern[O] { 119 | def &>(name: String, check: ValueCheck[Seq[String]]): PathWithPartialQuery[O] 120 | def &>(name: String, check: ValueCheck.Backtrack[Seq[String]]): PathWithPartialQuery[O] 121 | def &>[A](name: String, pattern: ValuePattern[Seq[String], A])( 122 | implicit growable: O <:< O with Args.Growable 123 | ): PathWithPartialQuery[O#Append[A]] 124 | def &>[A](name: String, pattern: ValuePattern.Backtrack[Seq[String], A])( 125 | implicit growable: O <:< O with Args.Growable 126 | ): PathWithPartialQuery[O#Append[A]] 127 | def &+(check: ValueCheck[Map[String, ParamValues]]): PathWithFullQuery[O] 128 | def &+(check: ValueCheck.Backtrack[Map[String, ParamValues]]): PathWithFullQuery[O] 129 | def &+[A](check: ValuePattern[Map[String, ParamValues], A])( 130 | implicit growable: O <:< O with Args.Growable 131 | ): PathWithFullQuery[O#Append[A]] 132 | def &+[A](check: ValuePattern.Backtrack[Map[String, ParamValues], A])( 133 | implicit growable: O <:< O with Args.Growable 134 | ): PathWithFullQuery[O#Append[A]] 135 | override def trace: PathTrace[Args._0, O, PathEnd.WithQuery] 136 | } 137 | 138 | sealed trait PathWithFullQuery[O <: Args] extends RoutePattern[O] { 139 | override def trace: PathTrace[Args._0, O, PathEnd.WithQuery] 140 | } 141 | 142 | private trait CompletePathTrace[O <: Args] { 143 | def apply[O1 <: Args, E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 144 | next: PathTrace[O, O1, E] 145 | ): PathTrace[Args._0, O1, E] 146 | } 147 | 148 | val Root: PartialPath[Args._0] = PartialPathImpl(new CompletePathTrace[Args._0] { 149 | override def apply[O1 <: Args, E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 150 | next: PathTrace[Args._0, O1, E] 151 | ): PathTrace[Args._0, O1, E] = next 152 | }) 153 | 154 | final private case class PartialPathImpl[I <: Args](complete: CompletePathTrace[I]) extends PartialPath[I] { 155 | override def />(check: ValueCheck[String]): PartialPath[I] = 156 | PartialPathImpl(new CompletePathTrace[I] { 157 | override def apply[O <: Args, E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 158 | next: PathTrace[I, O, E] 159 | ): PathTrace[Args._0, O, E] = 160 | complete(SegmentCheck(check, false, next)) 161 | }) 162 | override def />(check: ValueCheck.Force[String]): PartialPath[I] = 163 | PartialPathImpl(new CompletePathTrace[I] { 164 | override def apply[O <: Args, E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 165 | next: PathTrace[I, O, E] 166 | ): PathTrace[Args._0, O, E] = 167 | complete(SegmentCheck(ValueCheck(check.pattern), true, next)) 168 | }) 169 | override def />[A]( 170 | pattern: ValuePattern[String, A] 171 | )(implicit growable: I <:< I with Args.Growable): PartialPath[I#Append[A]] = 172 | PartialPathImpl(new CompletePathTrace[I#Append[A]] { 173 | override def apply[O <: Args, E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 174 | next: PathTrace[I#Append[A], O, E] 175 | ): PathTrace[Args._0, O, E] = 176 | complete(SegmentPattern(pattern, false, next, growable)) 177 | }) 178 | override def />[A]( 179 | pattern: ValuePattern.Force[String, A] 180 | )(implicit growable: I <:< I with Args.Growable): PartialPath[I#Append[A]] = 181 | PartialPathImpl(new CompletePathTrace[I#Append[A]] { 182 | override def apply[O <: Args, E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 183 | next: PathTrace[I#Append[A], O, E] 184 | ): PathTrace[Args._0, O, E] = 185 | complete(SegmentPattern(pattern.pattern, true, next, growable)) 186 | }) 187 | override def /+(check: ValueCheck[Seq[String]]): FullPath[I] = 188 | FullPathImpl(new CompletePathMatched[I] { 189 | override def apply[O <: Args, E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 190 | end: E[I, O] 191 | ): PathTrace[Args._0, O, E] = 192 | complete(SegmentsCheck(check, false, end)) 193 | }) 194 | override def /+(check: ValueCheck.Force[Seq[String]]): FullPath[I] = 195 | FullPathImpl(new CompletePathMatched[I] { 196 | override def apply[O <: Args, E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 197 | end: E[I, O] 198 | ): PathTrace[Args._0, O, E] = 199 | complete(SegmentsCheck(ValueCheck(check.pattern), true, end)) 200 | }) 201 | override def /+[A]( 202 | pattern: ValuePattern[Seq[String], A] 203 | )(implicit growable: I <:< I with Args.Growable): FullPath[I#Append[A]] = 204 | FullPathImpl(new CompletePathMatched[I#Append[A]] { 205 | override def apply[O <: Args, E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 206 | end: E[I#Append[A], O] 207 | ): PathTrace[Args._0, O, E] = 208 | complete(SegmentsPattern(pattern, false, end, growable)) 209 | }) 210 | override def /+[A]( 211 | pattern: ValuePattern.Force[Seq[String], A] 212 | )(implicit growable: I <:< I with Args.Growable): FullPath[I#Append[A]] = 213 | FullPathImpl(new CompletePathMatched[I#Append[A]] { 214 | override def apply[O <: Args, E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 215 | end: E[I#Append[A], O] 216 | ): PathTrace[Args._0, O, E] = 217 | complete(SegmentsPattern(pattern.pattern, true, end, growable)) 218 | }) 219 | override def ?>(name: String, check: ValueCheck[Seq[String]]): PathWithPartialQuery[I] = 220 | PartialQueryImpl(new CompleteQueryTrace[I] { 221 | override def apply[O <: Args](next: QueryTrace[I, O]): PathTrace[Args._0, O, PathEnd.WithQuery] = 222 | complete(PathMatched(PathEnd.WithQuery(ParamCheck(name, check, true, next)))) 223 | }) 224 | override def ?>(name: String, check: ValueCheck.Backtrack[Seq[String]]): PathWithPartialQuery[I] = 225 | PartialQueryImpl(new CompleteQueryTrace[I] { 226 | override def apply[O <: Args](next: QueryTrace[I, O]): PathTrace[Args._0, O, PathEnd.WithQuery] = 227 | complete(PathMatched(PathEnd.WithQuery(ParamCheck(name, ValueCheck(check.pattern), false, next)))) 228 | }) 229 | override def ?>[A](name: String, pattern: ValuePattern[Seq[String], A])( 230 | implicit growable: I <:< I with Args.Growable 231 | ): PathWithPartialQuery[I#Append[A]] = 232 | PartialQueryImpl(new CompleteQueryTrace[I#Append[A]] { 233 | override def apply[O <: Args](next: QueryTrace[I#Append[A], O]): PathTrace[Args._0, O, PathEnd.WithQuery] = 234 | complete(PathMatched(PathEnd.WithQuery(ParamPattern(name, pattern, true, next, growable)))) 235 | }) 236 | override def ?>[A](name: String, pattern: ValuePattern.Backtrack[Seq[String], A])( 237 | implicit growable: I <:< I with Args.Growable 238 | ): PathWithPartialQuery[I#Append[A]] = 239 | PartialQueryImpl(new CompleteQueryTrace[I#Append[A]] { 240 | override def apply[O <: Args](next: QueryTrace[I#Append[A], O]): PathTrace[Args._0, O, PathEnd.WithQuery] = 241 | complete(PathMatched(PathEnd.WithQuery(ParamPattern(name, pattern.pattern, false, next, growable)))) 242 | }) 243 | override def ?+(check: ValueCheck[Map[String, ParamValues]]): PathWithFullQuery[I] = 244 | FullQueryImpl(complete(PathMatched(PathEnd.WithQuery(ParamsCheck(check, true, Args.same[I]))))) 245 | override def ?+(check: ValueCheck.Backtrack[Map[String, ParamValues]]): PathWithFullQuery[I] = 246 | FullQueryImpl( 247 | complete(PathMatched(PathEnd.WithQuery(ParamsCheck(ValueCheck(check.pattern), false, Args.same[I])))) 248 | ) 249 | override def ?+[A]( 250 | pattern: ValuePattern[Map[String, ParamValues], A] 251 | )(implicit growable: I <:< I with Args.Growable): PathWithFullQuery[I#Append[A]] = 252 | FullQueryImpl( 253 | complete(PathMatched(PathEnd.WithQuery(ParamsPattern(pattern, true, Args.same[I#Append[A]], growable)))) 254 | ) 255 | override def ?+[A]( 256 | pattern: ValuePattern.Backtrack[Map[String, ParamValues], A] 257 | )(implicit growable: I <:< I with Args.Growable): PathWithFullQuery[I#Append[A]] = 258 | FullQueryImpl( 259 | complete( 260 | PathMatched(PathEnd.WithQuery(ParamsPattern(pattern.pattern, false, Args.same[I#Append[A]], growable))) 261 | ) 262 | ) 263 | override def trace: PathTrace[Args._0, I, PathEnd.WithoutQuery] = 264 | complete(PathMatched(PathEnd.WithoutQuery(Args.same[I]))) 265 | } 266 | 267 | private trait CompletePathMatched[O <: Args] { 268 | def apply[O1 <: Args, E[EI <: Args, EO <: Args] <: PathEnd[EI, EO]]( 269 | end: E[O, O1] 270 | ): PathTrace[Args._0, O1, E] 271 | } 272 | 273 | final private case class FullPathImpl[O <: Args](complete: CompletePathMatched[O]) extends FullPath[O] { 274 | override def ?>(name: String, check: ValueCheck[Seq[String]]): PathWithPartialQuery[O] = 275 | PartialQueryImpl(new CompleteQueryTrace[O] { 276 | override def apply[O1 <: Args](next: QueryTrace[O, O1]): PathTrace[Args._0, O1, PathEnd.WithQuery] = 277 | complete(PathEnd.WithQuery(ParamCheck(name, check, true, next))) 278 | }) 279 | override def ?>(name: String, check: ValueCheck.Backtrack[Seq[String]]): PathWithPartialQuery[O] = 280 | PartialQueryImpl(new CompleteQueryTrace[O] { 281 | override def apply[O1 <: Args](next: QueryTrace[O, O1]): PathTrace[Args._0, O1, PathEnd.WithQuery] = 282 | complete(PathEnd.WithQuery(ParamCheck(name, ValueCheck(check.pattern), false, next))) 283 | }) 284 | override def ?>[A](name: String, pattern: ValuePattern[Seq[String], A])( 285 | implicit growable: O <:< O with Args.Growable 286 | ): PathWithPartialQuery[O#Append[A]] = 287 | PartialQueryImpl(new CompleteQueryTrace[O#Append[A]] { 288 | override def apply[O1 <: Args](next: QueryTrace[O#Append[A], O1]): PathTrace[Args._0, O1, PathEnd.WithQuery] = 289 | complete(PathEnd.WithQuery(ParamPattern(name, pattern, true, next, growable))) 290 | }) 291 | override def ?>[A](name: String, pattern: ValuePattern.Backtrack[Seq[String], A])( 292 | implicit growable: O <:< O with Args.Growable 293 | ): PathWithPartialQuery[O#Append[A]] = 294 | PartialQueryImpl(new CompleteQueryTrace[O#Append[A]] { 295 | override def apply[O1 <: Args](next: QueryTrace[O#Append[A], O1]): PathTrace[Args._0, O1, PathEnd.WithQuery] = 296 | complete(PathEnd.WithQuery(ParamPattern(name, pattern.pattern, false, next, growable))) 297 | }) 298 | override def ?+(check: ValueCheck[Map[String, ParamValues]]): PathWithFullQuery[O] = 299 | FullQueryImpl(complete(PathEnd.WithQuery(ParamsCheck(check, true, Args.same[O])))) 300 | override def ?+(check: ValueCheck.Backtrack[Map[String, ParamValues]]): PathWithFullQuery[O] = 301 | FullQueryImpl( 302 | complete(PathEnd.WithQuery(ParamsCheck(ValueCheck(check.pattern), false, Args.same[O]))) 303 | ) 304 | override def ?+[A]( 305 | pattern: ValuePattern[Map[String, ParamValues], A] 306 | )(implicit growable: O <:< O with Args.Growable): PathWithFullQuery[O#Append[A]] = 307 | FullQueryImpl( 308 | complete(PathEnd.WithQuery(ParamsPattern(pattern, true, Args.same[O#Append[A]], growable))) 309 | ) 310 | override def ?+[A]( 311 | pattern: ValuePattern.Backtrack[Map[String, ParamValues], A] 312 | )(implicit growable: O <:< O with Args.Growable): PathWithFullQuery[O#Append[A]] = 313 | FullQueryImpl( 314 | complete( 315 | PathEnd.WithQuery(ParamsPattern(pattern.pattern, false, Args.same[O#Append[A]], growable)) 316 | ) 317 | ) 318 | override def trace: PathTrace[Args._0, O, PathEnd.WithoutQuery] = 319 | complete(PathEnd.WithoutQuery(Args.same[O])) 320 | } 321 | 322 | private trait CompleteQueryTrace[O <: Args] { 323 | def apply[O1 <: Args](next: QueryTrace[O, O1]): PathTrace[Args._0, O1, PathEnd.WithQuery] 324 | } 325 | 326 | final private case class PartialQueryImpl[O <: Args](complete: CompleteQueryTrace[O]) 327 | extends PathWithPartialQuery[O] { 328 | override def &>(name: String, check: ValueCheck[Seq[String]]): PathWithPartialQuery[O] = 329 | PartialQueryImpl(new CompleteQueryTrace[O] { 330 | override def apply[O1 <: Args](next: QueryTrace[O, O1]): PathTrace[Args._0, O1, PathEnd.WithQuery] = 331 | complete(ParamCheck(name, check, true, next)) 332 | }) 333 | override def &>(name: String, check: ValueCheck.Backtrack[Seq[String]]): PathWithPartialQuery[O] = 334 | PartialQueryImpl(new CompleteQueryTrace[O] { 335 | override def apply[O1 <: Args](next: QueryTrace[O, O1]): PathTrace[Args._0, O1, PathEnd.WithQuery] = 336 | complete(ParamCheck(name, ValueCheck(check.pattern), false, next)) 337 | }) 338 | override def &>[A](name: String, pattern: ValuePattern[Seq[String], A])( 339 | implicit growable: O <:< O with Args.Growable 340 | ): PathWithPartialQuery[O#Append[A]] = 341 | PartialQueryImpl(new CompleteQueryTrace[O#Append[A]] { 342 | override def apply[O1 <: Args](next: QueryTrace[O#Append[A], O1]): PathTrace[Args._0, O1, PathEnd.WithQuery] = 343 | complete(ParamPattern(name, pattern, true, next, growable)) 344 | }) 345 | override def &>[A](name: String, pattern: ValuePattern.Backtrack[Seq[String], A])( 346 | implicit growable: O <:< O with Args.Growable 347 | ): PathWithPartialQuery[O#Append[A]] = 348 | PartialQueryImpl(new CompleteQueryTrace[O#Append[A]] { 349 | override def apply[O1 <: Args](next: QueryTrace[O#Append[A], O1]): PathTrace[Args._0, O1, PathEnd.WithQuery] = 350 | complete(ParamPattern(name, pattern.pattern, false, next, growable)) 351 | }) 352 | override def &+(check: ValueCheck[Map[String, ParamValues]]): PathWithFullQuery[O] = 353 | FullQueryImpl(complete(ParamsCheck(check, true, Args.same[O]))) 354 | override def &+(check: ValueCheck.Backtrack[Map[String, ParamValues]]): PathWithFullQuery[O] = 355 | FullQueryImpl(complete(ParamsCheck(ValueCheck(check.pattern), false, Args.same[O]))) 356 | override def &+[A]( 357 | pattern: ValuePattern[Map[String, ParamValues], A] 358 | )(implicit growable: O <:< O with Args.Growable): PathWithFullQuery[O#Append[A]] = 359 | FullQueryImpl(complete(ParamsPattern(pattern, true, Args.same[O#Append[A]], growable))) 360 | override def &+[A]( 361 | pattern: ValuePattern.Backtrack[Map[String, ParamValues], A] 362 | )(implicit growable: O <:< O with Args.Growable): PathWithFullQuery[O#Append[A]] = 363 | FullQueryImpl(complete(ParamsPattern(pattern.pattern, false, Args.same[O#Append[A]], growable))) 364 | override def trace: PathTrace[Args._0, O, PathEnd.WithQuery] = complete(QueryMatched(Args.same[O])) 365 | } 366 | 367 | final private case class FullQueryImpl[O <: Args](trace: PathTrace[Args._0, O, PathEnd.WithQuery]) 368 | extends PathWithFullQuery[O] 369 | } 370 | -------------------------------------------------------------------------------- /core/src/main/scala/com/github/mvv/routineer/Routes.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.routineer 2 | 3 | import com.github.mvv.routineer.Route.NoHandler 4 | 5 | import scala.language.higherKinds 6 | 7 | sealed trait Routes[+H[_ <: Args]] { 8 | final def :+[O <: Args, H1[O1 <: Args] >: H[O1]](route: Route[O, H1]): Routes[H1] = 9 | this ++ Routes(route) 10 | final def +:[O <: Args, H1[O1 <: Args] >: H[O1]](route: Route[O, H1]): Routes[H1] = 11 | Routes(route) ++ this 12 | def ++[H1[O <: Args] >: H[O]](routes: Routes[H1]): Routes[H1] 13 | def /:(segment: String): Routes[H] 14 | def dispatch(path: Seq[String], query: Map[String, ParamValues] = Map.empty): Dispatch[H] 15 | } 16 | 17 | object Routes { 18 | val empty: Routes[Route.NoHandler] = Empty 19 | 20 | def apply[O <: Args, H[_ <: Args]](route: Route[O, H]): Routes[H] = 21 | NonEmpty(tracePath(route.pattern.trace, route.handler)) 22 | 23 | final class forHandler[H[_ <: Args]] { 24 | def apply[O <: Args](route: Route[O, H]): Routes[H] = Routes(route) 25 | } 26 | def forHandler[H[_ <: Args]]: forHandler[H] = new forHandler[H] 27 | 28 | private def tracePath[I <: Args, O <: Args, H[_ <: Args]](trace: RoutePattern.PathTrace[I, O, RoutePattern.PathEnd], 29 | handler: H[O]): PathTrace[I, H] = 30 | trace match { 31 | case RoutePattern.SegmentCheck(ValueCheck(EqualsP(segment: String)), false, next) => 32 | SegmentConst(segment, tracePath(next, handler)) 33 | case RoutePattern.SegmentCheck(check, force, next) => 34 | SegmentCheck(check, force, tracePath(next, handler)) 35 | case RoutePattern.SegmentPattern(pattern, force, next, growable) => 36 | SegmentPattern(pattern, force, tracePath(next, handler), growable) 37 | case RoutePattern.SegmentsCheck(check, force, end) => 38 | val next = end match { 39 | case RoutePattern.PathEnd.WithQuery(trace1) => 40 | traceQuery(trace1, handler) 41 | case RoutePattern.PathEnd.WithoutQuery(same) => 42 | same.symm.subst[({ type L[A <: Args] = QueryMatched[A, H] })#L](QueryMatched[O, H](handler)) 43 | } 44 | SegmentsCheck(check, force, next) 45 | case RoutePattern.SegmentsPattern(pattern, force, end, growable) => 46 | val next = end match { 47 | case RoutePattern.PathEnd.WithQuery(trace1) => 48 | traceQuery(trace1, handler) 49 | case RoutePattern.PathEnd.WithoutQuery(same) => 50 | same.symm.subst[({ type L[A <: Args] = QueryMatched[A, H] })#L](QueryMatched[O, H](handler)) 51 | } 52 | SegmentsPattern(pattern, force, next, growable) 53 | case RoutePattern.PathMatched(end) => 54 | val next = end match { 55 | case RoutePattern.PathEnd.WithQuery(trace1) => 56 | traceQuery(trace1, handler) 57 | case RoutePattern.PathEnd.WithoutQuery(same) => 58 | same.symm.subst[({ type L[A <: Args] = QueryMatched[A, H] })#L](QueryMatched[O, H](handler)) 59 | } 60 | PathMatched(next) 61 | } 62 | 63 | private def traceQuery[I <: Args, O <: Args, H[_ <: Args]](trace: RoutePattern.QueryTrace[I, O], 64 | handler: H[O]): QueryTrace[I, H] = 65 | trace match { 66 | case RoutePattern.ParamCheck(name, check, force, next) => 67 | ParamCheck(name, check, force, traceQuery(next, handler)) 68 | case RoutePattern.ParamPattern(name, pattern, force, next, growable) => 69 | ParamPattern(name, pattern, force, traceQuery(next, handler), growable) 70 | case RoutePattern.ParamsCheck(check, force, same) => 71 | ParamsCheck(check, force, same.symm.subst(handler)) 72 | case RoutePattern.ParamsPattern(pattern, force, same, growable) => 73 | ParamsPattern(pattern, force, same.symm.subst(handler), growable) 74 | case RoutePattern.QueryMatched(same) => 75 | QueryMatched(same.symm.subst(handler)) 76 | } 77 | 78 | sealed private trait Alts[+A <: Alts.Single[A]] { 79 | def ++[B >: A <: Alts.Single[B]](alts: Alts[B]): Alts[B] 80 | } 81 | 82 | private object Alts { 83 | def apply[A <: Single[A]](head: A, last: A): NonEmpty[A] = Two(head, last) 84 | 85 | case object Empty extends Alts[Nothing] { 86 | override def ++[B >: Nothing <: Single[B]](alts: Alts[B]): Alts[B] = alts 87 | } 88 | 89 | sealed trait NonEmpty[+A <: Single[A]] extends Alts[A] { 90 | override def ++[B >: A <: Single[B]](alts: Alts[B]): NonEmpty[B] 91 | 92 | def head: A 93 | 94 | def last: A 95 | 96 | def tail: Alts[A] 97 | 98 | def merge[B >: A <: Single[B]](merged: B, alts: NonEmpty[B]): NonEmpty[B] 99 | } 100 | 101 | sealed trait Single[+A <: Single[A]] extends NonEmpty[A] { 102 | self: A => 103 | override def ++[B >: A <: Single[B]](alts: Alts[B]): NonEmpty[B] = alts match { 104 | case Empty => this 105 | case single: Single[B] => Two(self, single.head) 106 | case two: Two[B] => More(self, Vector(two.head), two.last) 107 | case more: More[B] => More(self, more.head +: more.values, more.last) 108 | } 109 | 110 | override def head: A = self 111 | 112 | override def last: A = self 113 | 114 | override def tail: Alts[A] = Empty 115 | 116 | override def merge[B >: A <: Single[B]](merged: B, alts: NonEmpty[B]): NonEmpty[B] = alts match { 117 | case _: Single[B] => merged 118 | case two: Two[B] => Two(merged, two.last) 119 | case more: More[B] => More(merged, more.values, more.last) 120 | } 121 | } 122 | 123 | final private case class Two[+A <: Single[A]](head: A, last: A) extends NonEmpty[A] { 124 | override def ++[B >: A <: Single[B]](alts: Alts[B]): NonEmpty[B] = alts match { 125 | case Empty => this 126 | case single: Single[B] => More(head, Vector(last), single.head) 127 | case two: Two[B] => More(head, Vector(last, two.head), two.last) 128 | case more: More[B] => More(head, last +: more.head +: more.values, more.last) 129 | } 130 | 131 | override def tail: Alts[A] = last 132 | 133 | override def merge[B >: A <: Single[B]](merged: B, alts: NonEmpty[B]): NonEmpty[B] = alts match { 134 | case _: Single[B] => Two(head, merged) 135 | case two: Two[B] => More(head, Vector(merged), two.last) 136 | case more: More[B] => More(head, merged +: more.values, more.last) 137 | } 138 | } 139 | 140 | final private case class More[+A <: Single[A]](head: A, values: Seq[A], last: A) extends NonEmpty[A] { 141 | override def ++[B >: A <: Single[B]](alts: Alts[B]): NonEmpty[B] = alts match { 142 | case Empty => this 143 | case single: Single[B] => More(head, values :+ last, single.head) 144 | case two: Two[B] => More(head, values :+ last :+ two.head, two.last) 145 | case more: More[B] => More(head, (values :+ last :+ more.head) ++ more.values, more.last) 146 | } 147 | 148 | override def tail: Alts[A] = { 149 | val valuesTail = values.tail 150 | if (valuesTail.isEmpty) { 151 | Two(values.head, last) 152 | } else { 153 | More(values.head, valuesTail, last) 154 | } 155 | } 156 | 157 | override def merge[B >: A <: Single[B]](merged: B, alts: NonEmpty[B]): NonEmpty[B] = alts match { 158 | case _: Single[B] => More(head, values, merged) 159 | case two: Two[B] => More(head, values :+ merged, two.last) 160 | case more: More[B] => More(head, (values :+ merged) ++ more.values, more.last) 161 | } 162 | } 163 | 164 | } 165 | 166 | private object Empty extends Routes[Route.NoHandler] { 167 | override def ++[H1[O <: Args] >: Route.NoHandler[O]](routes: Routes[H1]): Routes[H1] = routes 168 | 169 | override def /:(prefix: String): Routes[Route.NoHandler] = this 170 | 171 | override def dispatch(path: Seq[String], query: Map[String, ParamValues]): Dispatch[NoHandler] = Dispatch.NotFound 172 | } 173 | 174 | final private case class NonEmpty[+H[_ <: Args]](trace: PathTrace[Args._0, H]) extends Routes[H] { 175 | override def ++[H1[O <: Args] >: H[O]](routes: Routes[H1]): Routes[H1] = routes match { 176 | case rs: NonEmpty[H1] => NonEmpty(trace ++ rs.trace) 177 | case Empty => this 178 | } 179 | 180 | override def /:(prefix: String): Routes[H] = NonEmpty(SegmentConst(prefix, trace)) 181 | 182 | override def dispatch(path: Seq[String], query: Map[String, ParamValues]): Dispatch[H] = 183 | Routes.dispatch( 184 | DispatchContext.Path(args = Args._0, fullPath = path, prefix = 0, path = path, query = query, trace = trace), 185 | Nil 186 | ) 187 | } 188 | 189 | sealed private trait DispatchContext[I <: Args, +H[_ <: Args]] 190 | 191 | private object DispatchContext { 192 | 193 | final case class Path[I <: Args, +H[_ <: Args]](args: I, 194 | fullPath: Seq[String], 195 | prefix: Int, 196 | path: Seq[String], 197 | query: Map[String, ParamValues], 198 | trace: PathTrace[I, H]) 199 | extends DispatchContext[I, H] 200 | 201 | final case class MorePath[I <: Args, +H[_ <: Args]](args: I, 202 | fullPath: Seq[String], 203 | prefix: Int, 204 | segment: String, 205 | segments: Seq[String], 206 | query: Map[String, ParamValues], 207 | alts: Alts[MorePathAlt[I, H]]) 208 | extends DispatchContext[I, H] 209 | 210 | final case class NoMorePath[I <: Args, +H[_ <: Args]](args: I, 211 | fullPath: Seq[String], 212 | prefix: Int, 213 | query: Map[String, ParamValues], 214 | alts: Alts[NoMorePathAlt[I, H]]) 215 | extends DispatchContext[I, H] 216 | 217 | final case class Query[I <: Args, +H[_ <: Args]](args: I, 218 | reversedPrefix: List[String], 219 | query: Map[String, ParamValues], 220 | alts: Alts[QueryAlt[I, H]]) 221 | extends DispatchContext[I, H] 222 | 223 | } 224 | 225 | /* 226 | final case class DispatchState[I, +H[_ <: Args]](context: DispatchContext[I, H], 227 | stack: List[DispatchContext[_ <: Args, H]]) 228 | */ 229 | 230 | @inline 231 | private def delayedDispatch[I <: Args, H[_ <: Args]]( 232 | context: DispatchContext[I, H], 233 | stack: List[DispatchContext[_ <: Args, H]] 234 | ): () => Dispatch[H] = { () => 235 | dispatch(context, stack) 236 | } 237 | 238 | // Scala 2.11 cannot handle @tailrec here 239 | private def dispatch[H[_ <: Args]](context0: DispatchContext[_ <: Args, H], 240 | stack0: List[DispatchContext[_ <: Args, H]]): Dispatch[H] = { 241 | var context = context0 242 | var stack = stack0 243 | while (true) { 244 | context match { 245 | case c: DispatchContext.Path[_, H] => 246 | c.path.headOption match { 247 | case Some(segment) => 248 | context = DispatchContext.MorePath(args = c.args, 249 | fullPath = c.fullPath, 250 | prefix = c.prefix, 251 | segment = segment, 252 | segments = c.path, 253 | query = c.query, 254 | alts = c.trace.path) 255 | case None => 256 | context = DispatchContext.NoMorePath(args = c.args, 257 | fullPath = c.fullPath, 258 | prefix = c.prefix, 259 | query = c.query, 260 | alts = c.trace.noPath) 261 | } 262 | case c: DispatchContext.MorePath[_, H] => 263 | c.alts match { 264 | case alts: Alts.NonEmpty[MorePathAlt[_, H]] => 265 | alts.head match { 266 | case alt: SegmentConst[_, H] => 267 | if (alt.segment == c.segment) { 268 | context = DispatchContext.Path(args = c.args, 269 | fullPath = c.fullPath, 270 | prefix = c.prefix + 1, 271 | path = c.segments.tail, 272 | query = c.query, 273 | trace = alt.next) 274 | stack = c.copy(alts = alts.tail) :: stack 275 | } else { 276 | context = c.copy(alts = alts.tail) 277 | } 278 | case alt: SegmentConsts[_, H] => 279 | alt.segments.get(c.segment) match { 280 | case Some(next) => 281 | context = DispatchContext.Path(args = c.args, 282 | fullPath = c.fullPath, 283 | prefix = c.prefix + 1, 284 | path = c.segments.tail, 285 | query = c.query, 286 | trace = next) 287 | stack = c.copy(alts = alts.tail) :: stack 288 | case None => 289 | context = c.copy(alts = alts.tail) 290 | } 291 | case alt: SegmentCheck[_, H] => 292 | alt.check.pattern(c.segment) match { 293 | case ValuePattern.Matched(_) => 294 | context = DispatchContext.Path(args = c.args, 295 | fullPath = c.fullPath, 296 | prefix = c.prefix + 1, 297 | path = c.segments.tail, 298 | query = c.query, 299 | trace = alt.next) 300 | stack = c.copy(alts = alts.tail) :: stack 301 | case ValuePattern.NotMatched(first, rest @ _*) if alt.force => 302 | return Dispatch.Failure(Dispatch.InSegment(c.fullPath.take(c.prefix), c.segment), first, rest: _*) 303 | case ValuePattern.NotMatched(_, _) => 304 | context = c.copy(alts = alts.tail) 305 | } 306 | case alt: SegmentPattern[_, _, H] => 307 | alt.pattern(c.segment) match { 308 | case ValuePattern.Matched(value) => 309 | context = DispatchContext.Path(args = alt.growable(c.args).append(value), 310 | fullPath = c.fullPath, 311 | prefix = c.prefix + 1, 312 | path = c.segments.tail, 313 | query = c.query, 314 | trace = alt.next) 315 | stack = c.copy(alts = alts.tail) :: stack 316 | case ValuePattern.NotMatched(first, rest @ _*) if alt.force => 317 | return Dispatch.Failure(Dispatch.InSegment(c.fullPath.take(c.prefix), c.segment), first, rest: _*) 318 | case ValuePattern.NotMatched(_, _) => 319 | context = c.copy(alts = alts.tail) 320 | } 321 | case alt: SegmentsCheck[_, H] => 322 | alt.check.pattern(c.segments) match { 323 | case ValuePattern.Matched(_) => 324 | context = DispatchContext.Query(args = c.args, 325 | reversedPrefix = Nil, 326 | query = c.query, 327 | alts = alt.next.alts) 328 | stack = c.copy(alts = alts.tail) :: stack 329 | case ValuePattern.NotMatched(first, rest @ _*) if alt.force => 330 | return Dispatch.Failure(Dispatch.InSegments(c.fullPath.take(c.prefix), c.segments), 331 | first, 332 | rest: _*) 333 | case ValuePattern.NotMatched(_, _) => 334 | context = c.copy(alts = alts.tail) 335 | } 336 | case alt: SegmentsPattern[_, _, H] => 337 | alt.pattern(c.segments) match { 338 | case ValuePattern.Matched(value) => 339 | context = DispatchContext.Query(args = alt.growable(c.args).append(value), 340 | reversedPrefix = Nil, 341 | query = c.query, 342 | alts = alt.next.alts) 343 | stack = c.copy(alts = alts.tail) :: stack 344 | case ValuePattern.NotMatched(first, rest @ _*) if alt.force => 345 | return Dispatch.Failure(Dispatch.InSegments(c.fullPath.take(c.prefix), c.segments), 346 | first, 347 | rest: _*) 348 | case ValuePattern.NotMatched(_, _) => 349 | context = c.copy(alts = alts.tail) 350 | } 351 | } 352 | case Alts.Empty => 353 | stack.headOption match { 354 | case Some(next) => 355 | context = next 356 | stack = stack.tail 357 | case None => 358 | return Dispatch.NotFound 359 | } 360 | } 361 | case c: DispatchContext.NoMorePath[_, H] => 362 | c.alts match { 363 | case alts: Alts.NonEmpty[NoMorePathAlt[_, H]] => 364 | alts.head match { 365 | case alt: PathMatched[_, H] => 366 | context = 367 | DispatchContext.Query(args = c.args, reversedPrefix = Nil, query = c.query, alts = alt.next.alts) 368 | stack = c.copy(alts = alts.tail) :: stack 369 | case alt: SegmentsCheck[_, H] => 370 | alt.check.pattern(Nil) match { 371 | case ValuePattern.Matched(_) => 372 | context = DispatchContext.Query(args = c.args, 373 | reversedPrefix = Nil, 374 | query = c.query, 375 | alts = alt.next.alts) 376 | stack = c.copy(alts = alts.tail) :: stack 377 | case ValuePattern.NotMatched(first, rest @ _*) if alt.force => 378 | return Dispatch.Failure(Dispatch.InSegments(c.fullPath.take(c.prefix), Nil), first, rest: _*) 379 | case ValuePattern.NotMatched(_, _) => 380 | context = c.copy(alts = alts.tail) 381 | } 382 | case alt: SegmentsPattern[_, _, H] => 383 | alt.pattern(Nil) match { 384 | case ValuePattern.Matched(value) => 385 | context = DispatchContext.Query(args = alt.growable(c.args).append(value), 386 | reversedPrefix = Nil, 387 | query = c.query, 388 | alts = alt.next.alts) 389 | stack = c.copy(alts = alts.tail) :: stack 390 | case ValuePattern.NotMatched(first, rest @ _*) if alt.force => 391 | return Dispatch.Failure(Dispatch.InSegments(c.fullPath.take(c.prefix), Nil), first, rest: _*) 392 | case ValuePattern.NotMatched(_, _) => 393 | context = c.copy(alts = alts.tail) 394 | } 395 | } 396 | case Alts.Empty => 397 | stack.headOption match { 398 | case Some(next) => 399 | context = next 400 | stack = stack.tail 401 | case None => 402 | return Dispatch.NotFound 403 | } 404 | } 405 | case c: DispatchContext.Query[_, H] => 406 | c.alts match { 407 | case alts: Alts.NonEmpty[QueryAlt[_, H]] => 408 | alts.head match { 409 | case alt: QueryMatched[_, H] => 410 | return Dispatch.Handler(c.args, alt.handler, delayedDispatch(c.copy(alts = alts.tail), stack)) 411 | case alt: ParamCheck[_, H] => 412 | val values = c.query.get(alt.name).map(_.toSeq).getOrElse(Nil) 413 | alt.check.pattern(values) match { 414 | case ValuePattern.Matched(_) => 415 | context = DispatchContext.Query(args = c.args, 416 | reversedPrefix = alt.name :: c.reversedPrefix, 417 | query = c.query - alt.name, 418 | alts = alt.next.alts) 419 | stack = c.copy(alts = alts.tail) :: stack 420 | case ValuePattern.NotMatched(first, rest @ _*) if alt.force => 421 | return Dispatch.Failure(Dispatch.InParam(c.reversedPrefix.reverse, alt.name, values), 422 | first, 423 | rest: _*) 424 | case ValuePattern.NotMatched(_, _) => 425 | context = c.copy(alts = alts.tail) 426 | } 427 | case alt: ParamPattern[_, _, H] => 428 | val values = c.query.get(alt.name).map(_.toSeq).getOrElse(Nil) 429 | alt.pattern(values) match { 430 | case ValuePattern.Matched(value) => 431 | context = DispatchContext.Query(args = alt.growable(c.args).append(value), 432 | reversedPrefix = alt.name :: c.reversedPrefix, 433 | query = c.query - alt.name, 434 | alts = alt.next.alts) 435 | stack = c.copy(alts = alts.tail) :: stack 436 | case ValuePattern.NotMatched(first, rest @ _*) if alt.force => 437 | return Dispatch.Failure(Dispatch.InParam(c.reversedPrefix.reverse, alt.name, values), 438 | first, 439 | rest: _*) 440 | case ValuePattern.NotMatched(_, _) => 441 | context = c.copy(alts = alts.tail) 442 | } 443 | case alt: ParamsCheck[_, H] => 444 | alt.check.pattern(c.query) match { 445 | case ValuePattern.Matched(_) => 446 | return Dispatch.Handler(c.args, alt.handler, delayedDispatch(c.copy(alts = alts.tail), stack)) 447 | case ValuePattern.NotMatched(first, rest @ _*) if alt.force => 448 | return Dispatch.Failure(Dispatch.InParams(c.reversedPrefix.reverse, c.query), first, rest: _*) 449 | case ValuePattern.NotMatched(_, _) => 450 | context = c.copy(alts = alts.tail) 451 | } 452 | case alt: ParamsPattern[_, _, H] => 453 | alt.pattern(c.query) match { 454 | case ValuePattern.Matched(value) => 455 | return Dispatch.Handler(alt.growable(c.args).append(value), 456 | alt.handler, 457 | delayedDispatch(c.copy(alts = alts.tail), stack)) 458 | case ValuePattern.NotMatched(first, rest @ _*) if alt.force => 459 | return Dispatch.Failure(Dispatch.InParams(c.reversedPrefix.reverse, c.query), first, rest: _*) 460 | case ValuePattern.NotMatched(_, _) => 461 | context = c.copy(alts = alts.tail) 462 | } 463 | } 464 | case Alts.Empty => 465 | stack.headOption match { 466 | case Some(next) => 467 | context = next 468 | stack = stack.tail 469 | case None => 470 | return Dispatch.NotFound 471 | } 472 | } 473 | } 474 | } 475 | throw new RuntimeException("Unreachable code") 476 | } 477 | 478 | sealed private trait PathTrace[I <: Args, +H[_ <: Args]] { 479 | def path: Alts[MorePathAlt[I, H]] 480 | def noPath: Alts[NoMorePathAlt[I, H]] 481 | def ++[H1[O <: Args] >: H[O]](trace: PathTrace[I, H1]): PathTrace[I, H1] = { 482 | val (path1, combinedCache) = (path, trace.path) match { 483 | case (Alts.Empty, result) => (result, None) 484 | case (result, Alts.Empty) => (result, None) 485 | case (nePath: Alts.NonEmpty[MorePathAlt[I, H]], neTracePath: Alts.NonEmpty[MorePathAlt[I, H1]]) => 486 | nePath.last.combine(neTracePath.head) match { 487 | case Some(combined) => 488 | (nePath.merge(combined, neTracePath), Some((nePath.last, neTracePath.head, combined))) 489 | case None => 490 | (nePath ++ neTracePath, None) 491 | } 492 | } 493 | val noPath1 = (noPath, trace.noPath) match { 494 | case (Alts.Empty, result) => result 495 | case (result, Alts.Empty) => result 496 | case (neNoPath: Alts.NonEmpty[NoMorePathAlt[I, H]], neTraceNoPath: Alts.NonEmpty[NoMorePathAlt[I, H1]]) => 497 | combinedCache match { 498 | case Some((last, head, combined)) if (neNoPath.last eq last) && (neTraceNoPath.head eq head) => 499 | neNoPath.merge(combined.asInstanceOf[NoMorePathAlt[I, H1]], neTraceNoPath) 500 | case _ => 501 | neNoPath.last.combine(neTraceNoPath.head) match { 502 | case Some(combined) => neNoPath.merge(combined, neTraceNoPath) 503 | case None => neNoPath ++ neTraceNoPath 504 | } 505 | } 506 | } 507 | (path1, noPath1) match { 508 | case (Alts.Empty, singleNoPath) => singleNoPath.asInstanceOf[NoMorePathAlt[I, H1]] 509 | case (singlePath: MorePathAlt[I, H1], Alts.Empty) => singlePath 510 | case (singlePath: MorePathAlt[I, H1], singleNoPath: NoMorePathAlt[I, H1]) if singlePath eq singleNoPath => 511 | singlePath 512 | case (nePath: Alts.NonEmpty[MorePathAlt[I, H1]], _) => 513 | PathAlts(nePath, noPath1) 514 | } 515 | } 516 | } 517 | 518 | sealed private trait PathAlt[I <: Args, +H[_ <: Args]] extends PathTrace[I, H] with Alts.Single[PathAlt[I, H]] { 519 | def combine[H1[O <: Args] >: H[O]](alt: PathAlt[I, H1]): Option[PathAlt[I, H1]] 520 | } 521 | sealed private trait MorePathAlt[I <: Args, +H[_ <: Args]] extends PathAlt[I, H] with Alts.Single[MorePathAlt[I, H]] { 522 | final override def path: MorePathAlt[I, H] = this 523 | override def combine[H1[O <: Args] >: H[O]](alt: PathAlt[I, H1]): Option[MorePathAlt[I, H1]] 524 | } 525 | sealed private trait SegmentAlt[I <: Args, +H[_ <: Args]] extends MorePathAlt[I, H] { 526 | final override def noPath: Alts[NoMorePathAlt[I, H]] = Alts.Empty 527 | } 528 | 529 | final private case class SegmentConst[I <: Args, +H[_ <: Args]](segment: String, next: PathTrace[I, H]) 530 | extends SegmentAlt[I, H] { 531 | override def combine[H1[O <: Args] >: H[O]](alt: PathAlt[I, H1]): Option[MorePathAlt[I, H1]] = 532 | alt match { 533 | case t: SegmentConst[I, H1] if t.segment == segment => 534 | Some(SegmentConst(segment, next ++ t.next)) 535 | case t: SegmentConst[I, H1] => 536 | Some(SegmentConsts(Map(segment -> next, t.segment -> t.next))) 537 | case t: SegmentConsts[I, H1] => 538 | Some { 539 | SegmentConsts { 540 | t.segments.get(segment) match { 541 | case Some(next1) => t.segments.updated(segment, next ++ next1) 542 | case None => t.segments.updated(segment, next) 543 | } 544 | } 545 | } 546 | case _ => None 547 | } 548 | } 549 | 550 | final private case class SegmentConsts[I <: Args, +H[_ <: Args]](segments: Map[String, PathTrace[I, H]]) 551 | extends SegmentAlt[I, H] { 552 | override def combine[H1[O <: Args] >: H[O]](alt: PathAlt[I, H1]): Option[MorePathAlt[I, H1]] = 553 | alt match { 554 | case t: SegmentConst[I, H1] => 555 | Some { 556 | SegmentConsts { 557 | segments.get(t.segment) match { 558 | case Some(next) => segments.updated(t.segment, next ++ t.next) 559 | case None => segments.updated(t.segment, t.next) 560 | } 561 | } 562 | } 563 | case t: SegmentConsts[I, H1] => 564 | Some { 565 | SegmentConsts { 566 | t.segments.foldLeft(segments: Map[String, PathTrace[I, H1]]) { 567 | case (acc, (segment, next1)) => 568 | acc.get(segment) match { 569 | case Some(next) => acc.updated(segment, next ++ next1) 570 | case None => acc.updated(segment, next1) 571 | } 572 | } 573 | } 574 | } 575 | case _ => None 576 | } 577 | } 578 | 579 | final private case class SegmentCheck[I <: Args, +H[_ <: Args]](check: ValueCheck[String], 580 | force: Boolean, 581 | next: PathTrace[I, H]) 582 | extends SegmentAlt[I, H] { 583 | override def combine[H1[O <: Args] >: H[O]](alt: PathAlt[I, H1]): Option[MorePathAlt[I, H1]] = 584 | alt match { 585 | case t: SegmentCheck[I, H1] if t.check == check && t.force == force => 586 | Some(SegmentCheck(check, force, next ++ t.next)) 587 | case _ => None 588 | } 589 | } 590 | 591 | final private case class SegmentPattern[R, I <: Args, +H[_ <: Args]](pattern: ValuePattern[String, R], 592 | force: Boolean, 593 | next: PathTrace[I#Append[R], H], 594 | growable: I <:< I with Args.Growable) 595 | extends SegmentAlt[I, H] { 596 | override def combine[H1[O <: Args] >: H[O]](alt: PathAlt[I, H1]): Option[MorePathAlt[I, H1]] = 597 | alt match { 598 | case t: SegmentPattern[_, I, H1] if t.pattern == pattern && t.force == force => 599 | Some(SegmentPattern(pattern, force, next ++ t.next.asInstanceOf[PathTrace[I#Append[R], H1]], growable)) 600 | case _ => None 601 | } 602 | } 603 | 604 | sealed private trait NoMorePathAlt[I <: Args, +H[_ <: Args]] 605 | extends PathAlt[I, H] 606 | with Alts.Single[NoMorePathAlt[I, H]] { 607 | final override def noPath: NoMorePathAlt[I, H] = this 608 | override def combine[H1[O <: Args] >: H[O]](alt: PathAlt[I, H1]): Option[NoMorePathAlt[I, H1]] 609 | } 610 | sealed private trait SegmentsAlt[I <: Args, +H[_ <: Args]] 611 | extends MorePathAlt[I, H] 612 | with NoMorePathAlt[I, H] 613 | with Alts.Single[SegmentsAlt[I, H]] { 614 | override def combine[H1[O <: Args] >: H[O]](alt: PathAlt[I, H1]): Option[SegmentsAlt[I, H1]] 615 | } 616 | 617 | final private case class SegmentsCheck[I <: Args, +H[_ <: Args]](check: ValueCheck[Seq[String]], 618 | force: Boolean, 619 | next: QueryTrace[I, H]) 620 | extends SegmentsAlt[I, H] { 621 | override def combine[H1[O <: Args] >: H[O]](alt: PathAlt[I, H1]): Option[SegmentsAlt[I, H1]] = 622 | alt match { 623 | case t: SegmentsCheck[I, H1] if t.check == check && t.force == force => 624 | Some(SegmentsCheck(check, force, next ++ t.next)) 625 | case _ => None 626 | } 627 | } 628 | 629 | final private case class SegmentsPattern[R, I <: Args, +H[_ <: Args]](pattern: ValuePattern[Seq[String], R], 630 | force: Boolean, 631 | next: QueryTrace[I#Append[R], H], 632 | growable: I <:< I with Args.Growable) 633 | extends SegmentsAlt[I, H] { 634 | override def combine[H1[O <: Args] >: H[O]](alt: PathAlt[I, H1]): Option[SegmentsAlt[I, H1]] = 635 | alt match { 636 | case t: SegmentsPattern[_, I, H1] if t.pattern == pattern && t.force == force => 637 | Some(SegmentsPattern(pattern, force, next ++ t.next.asInstanceOf[QueryTrace[I#Append[R], H1]], growable)) 638 | case _ => None 639 | } 640 | } 641 | 642 | final private case class PathMatched[I <: Args, +H[_ <: Args]](next: QueryTrace[I, H]) extends NoMorePathAlt[I, H] { 643 | override def path: Alts[MorePathAlt[I, H]] = Alts.Empty 644 | override def combine[H1[O <: Args] >: H[O]](alt: PathAlt[I, H1]): Option[NoMorePathAlt[I, H1]] = 645 | alt match { 646 | case t: PathMatched[I, H1] => Some(PathMatched(next ++ t.next)) 647 | case _ => None 648 | } 649 | } 650 | 651 | final private case class PathAlts[I <: Args, +H[_ <: Args]](path: Alts.NonEmpty[MorePathAlt[I, H]], 652 | noPath: Alts[NoMorePathAlt[I, H]]) 653 | extends PathTrace[I, H] 654 | 655 | sealed private trait QueryTrace[I <: Args, +H[_ <: Args]] { 656 | def alts: Alts.NonEmpty[QueryAlt[I, H]] 657 | final def ++[H1[O <: Args] >: H[O]](trace: QueryTrace[I, H1]): QueryTrace[I, H1] = { 658 | val alts1 = alts.last.combine(trace.alts.head) match { 659 | case Some(combined) => alts.merge(combined, trace.alts) 660 | case None => alts ++ trace.alts 661 | } 662 | alts1 match { 663 | case alt: QueryAlt[I, H1] => alt 664 | case _ => QueryAlts(alts1) 665 | } 666 | } 667 | } 668 | 669 | sealed private trait QueryAlt[I <: Args, +H[_ <: Args]] extends QueryTrace[I, H] with Alts.Single[QueryAlt[I, H]] { 670 | final override def alts: QueryAlt[I, H] = this 671 | def combine[H1[O <: Args] >: H[O]](alt: QueryAlt[I, H1]): Option[QueryAlt[I, H1]] 672 | } 673 | 674 | final private case class ParamCheck[I <: Args, +H[_ <: Args]](name: String, 675 | check: ValueCheck[Seq[String]], 676 | force: Boolean, 677 | next: QueryTrace[I, H]) 678 | extends QueryAlt[I, H] { 679 | override def combine[H1[O <: Args] >: H[O]](alt: QueryAlt[I, H1]): Option[QueryAlt[I, H1]] = alt match { 680 | case t: ParamCheck[I, H1] if t.check == check && t.force == force => 681 | Some(ParamCheck(name, check, force, next ++ t.next)) 682 | case _ => None 683 | } 684 | } 685 | 686 | final private case class ParamPattern[R, I <: Args, +H[_ <: Args]](name: String, 687 | pattern: ValuePattern[Seq[String], R], 688 | force: Boolean, 689 | next: QueryTrace[I#Append[R], H], 690 | growable: I <:< I with Args.Growable) 691 | extends QueryAlt[I, H] { 692 | override def combine[H1[O <: Args] >: H[O]](alt: QueryAlt[I, H1]): Option[QueryAlt[I, H1]] = alt match { 693 | case t: ParamPattern[_, I, H1] if t.pattern == pattern && t.force == force => 694 | Some(ParamPattern(name, pattern, force, next ++ t.next.asInstanceOf[QueryTrace[I#Append[R], H1]], growable)) 695 | case _ => None 696 | } 697 | } 698 | 699 | final private case class ParamsCheck[I <: Args, +H[_ <: Args]](check: ValueCheck[Map[String, ParamValues]], 700 | force: Boolean, 701 | handler: H[I]) 702 | extends QueryAlt[I, H] { 703 | override def combine[H1[O <: Args] >: H[O]](alt: QueryAlt[I, H1]): Option[QueryAlt[I, H1]] = None 704 | } 705 | 706 | final private case class ParamsPattern[R, I <: Args, +H[_ <: Args]]( 707 | pattern: ValuePattern[Map[String, ParamValues], R], 708 | force: Boolean, 709 | handler: H[I#Append[R]], 710 | growable: I <:< I with Args.Growable 711 | ) extends QueryAlt[I, H] { 712 | override def combine[H1[O <: Args] >: H[O]](alt: QueryAlt[I, H1]): Option[QueryAlt[I, H1]] = None 713 | } 714 | 715 | final private case class QueryMatched[I <: Args, +H[_ <: Args]](handler: H[I]) extends QueryAlt[I, H] { 716 | override def combine[H1[O <: Args] >: H[O]](alt: QueryAlt[I, H1]): Option[QueryAlt[I, H1]] = None 717 | } 718 | 719 | final private case class QueryAlts[I <: Args, +H[_ <: Args]](alts: Alts.NonEmpty[QueryAlt[I, H]]) 720 | extends QueryTrace[I, H] 721 | } 722 | -------------------------------------------------------------------------------- /core/src/main/scala/com/github/mvv/routineer/ValueCheck.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer 18 | 19 | final case class ValueCheck[-I](pattern: ValuePattern[I, _]) { 20 | def ! : ValueCheck.Force[I] = ValueCheck.Force(pattern) 21 | def ~ : ValueCheck.Backtrack[I] = ValueCheck.Backtrack(pattern) 22 | } 23 | 24 | object ValueCheck { 25 | final case class Force[-I](pattern: ValuePattern[I, _]) 26 | final case class Backtrack[-I](pattern: ValuePattern[I, _]) 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/scala/com/github/mvv/routineer/ValuePattern.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2012, 2019 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer 18 | 19 | import scala.util.matching.Regex 20 | import scala.language.implicitConversions 21 | import scala.util.{Failure, Success, Try} 22 | 23 | trait ValuePattern[-I, +O] { 24 | def apply(in: I): ValuePattern.Match[O] 25 | final def contramap[I1](f: I1 => I): ValuePattern[I1, O] = 26 | ValuePattern.Chain(ValuePattern.Conv(f), this) 27 | final def map[O1](f: O => O1): ValuePattern[I, O1] = 28 | ValuePattern.Chain(this, ValuePattern.Conv(f)) 29 | final def >>>[I1 >: O, O1](pat: ValuePattern[I1, O1]): ValuePattern[I, O1] = 30 | ValuePattern.Chain(this, pat) 31 | final def &&&[I1 <: I, O1](pat: ValuePattern[I1, O1]): ValuePattern[I1, (O, O1)] = 32 | ValuePattern.Fanout(this, pat) 33 | final def ***[I1, O1](pat: ValuePattern[I1, O1]): ValuePattern[(I, I1), (O, O1)] = 34 | ValuePattern.Split(this, pat) 35 | final def check: ValueCheck[I] = ValueCheck(this) 36 | def ! : ValuePattern.Force[I, O] = ValuePattern.Force(this) 37 | def ~ : ValuePattern.Backtrack[I, O] = ValuePattern.Backtrack(this) 38 | } 39 | object ValuePattern { 40 | final case class Force[-I, +O](pattern: ValuePattern[I, O]) 41 | final case class Backtrack[-I, +O](pattern: ValuePattern[I, O]) 42 | 43 | sealed trait Match[+O] { 44 | def map[O1](f: O => O1): Match[O1] 45 | def sum[O1](other: Match[O1]): Match[(O, O1)] 46 | def toOption: Option[O] 47 | } 48 | object Match { 49 | def fromTry[O](tried: Try[O]): Match[O] = tried match { 50 | case Success(value) => Matched(value) 51 | case Failure(e) => NotMatched(Option(e.getMessage).getOrElse(e.getClass.getCanonicalName)) 52 | } 53 | } 54 | 55 | final case class Matched[+O](value: O) extends Match[O] { 56 | override def map[O1](f: O => O1): Match[O1] = Matched(f(value)) 57 | override def sum[O1](other: Match[O1]): Match[(O, O1)] = other match { 58 | case Matched(otherValue) => Matched((value, otherValue)) 59 | case reasons: NotMatched => reasons 60 | } 61 | override def toOption: Option[O] = Some(value) 62 | } 63 | 64 | final case class NotMatched(first: String, rest: String*) extends Match[Nothing] { 65 | override def map[O1](f: Nothing => O1): Match[O1] = this 66 | override def sum[O1](other: Match[O1]): Match[(Nothing, O1)] = other match { 67 | case Matched(_) => this 68 | case other: NotMatched => this ++ other 69 | } 70 | override def toOption: Option[Nothing] = None 71 | def ++(other: NotMatched): NotMatched = NotMatched(first, (rest ++ other.reasons): _*) 72 | def reasons: Seq[String] = first +: rest 73 | } 74 | 75 | final private case class Lifted[-I, +O](f: I => Match[O]) extends ValuePattern[I, O] { 76 | def apply(in: I): Match[O] = f(in) 77 | } 78 | final private case class Conv[-I, +O](f: I => O) extends ValuePattern[I, O] { 79 | def apply(in: I): Match[O] = Matched(f(in)) 80 | } 81 | final private case class Chain[-I, O, +O1](first: ValuePattern[I, O], second: ValuePattern[O, O1]) 82 | extends ValuePattern[I, O1] { 83 | def apply(in: I): Match[O1] = first.apply(in) match { 84 | case Matched(out) => second(out) 85 | case reasons: NotMatched => reasons 86 | } 87 | } 88 | final private case class Fanout[-I, +O1, +O2](first: ValuePattern[I, O1], second: ValuePattern[I, O2]) 89 | extends ValuePattern[I, (O1, O2)] { 90 | def apply(in: I): Match[(O1, O2)] = first.apply(in).sum(second.apply(in)) 91 | } 92 | final private case class Split[-I1, -I2, +O1, +O2](first: ValuePattern[I1, O1], second: ValuePattern[I2, O2]) 93 | extends ValuePattern[(I1, I2), (O1, O2)] { 94 | def apply(in: (I1, I2)): Match[(O1, O2)] = first.apply(in._1).sum(second.apply(in._2)) 95 | } 96 | 97 | /** Use a matching function as a [[ValuePattern]] */ 98 | implicit def apply[I, O](f: I => Match[O]): ValuePattern[I, O] = ValuePattern.Lifted(f) 99 | 100 | /** Use a function as a [[ValuePattern]] */ 101 | def map[I, O](f: I => O): ValuePattern[I, O] = ValuePattern.Conv(f) 102 | 103 | private val idInstance: ValuePattern[Any, Any] = { value: Any => 104 | Matched(value) 105 | } 106 | def id[A]: ValuePattern[A, A] = idInstance.asInstanceOf[ValuePattern[A, A]] 107 | } 108 | 109 | /** A [[ValuePattern]] that accepts only the provided value */ 110 | final case class EqualsP[A](value: A) extends ValuePattern[A, A] { 111 | override def apply(in: A): ValuePattern.Match[A] = 112 | if (in == value) ValuePattern.Matched(in) else ValuePattern.NotMatched(s"Value '$in' is not equal to '$value'") 113 | } 114 | 115 | /** A [[ValuePattern]] that matches textual representations of `Int` values 116 | * (using the provided radix). 117 | */ 118 | sealed class IntP private (val radix: Int) extends ValuePattern[String, Int] { 119 | final override def apply(in: String): ValuePattern.Match[Int] = 120 | ValuePattern.Match.fromTry(Try(java.lang.Integer.parseInt(in, radix))) 121 | override def toString: String = s"IntP($radix)" 122 | override def hashCode: Int = radix 123 | override def equals(that: Any): Boolean = that match { 124 | case that: IntP => radix == that.radix 125 | case _ => false 126 | } 127 | } 128 | 129 | /** Matches `Int` values written in the decimal numeral system. */ 130 | object IntP extends IntP(10) { 131 | private val MaxRadix = 36 132 | private val patterns = 133 | (2 to MaxRadix).map { r => 134 | if (r == 10) IntP else new IntP(r) 135 | }.toArray 136 | def apply(radix: Int): IntP = { 137 | require(radix >= 2 && radix <= MaxRadix) 138 | patterns(radix - 2) 139 | } 140 | def unapply(pat: IntP) = Some(pat.radix) 141 | } 142 | 143 | /** Matches non-negative numeric values. */ 144 | final case class NonNegativeP[A]()(implicit num: Numeric[A]) extends ValuePattern[A, A] { 145 | override def apply(in: A): ValuePattern.Match[A] = 146 | if (num.gteq(in, num.zero)) ValuePattern.Matched(in) else ValuePattern.NotMatched(s"Negative value $in") 147 | } 148 | 149 | /** Matches positive numeric values. */ 150 | final case class PositiveP[A]()(implicit num: Numeric[A]) extends ValuePattern[A, A] { 151 | override def apply(in: A): ValuePattern.Match[A] = 152 | if (num.gt(in, num.zero)) ValuePattern.Matched(in) else ValuePattern.NotMatched(s"Non-positive value $in") 153 | } 154 | 155 | /** Use a regular expression as a [[ValuePattern]]. */ 156 | final case class RegexP(regex: Regex) extends ValuePattern[String, Seq[String]] { 157 | override def apply(in: String): ValuePattern.Match[Seq[String]] = regex.unapplySeq(in) match { 158 | case Some(values) => ValuePattern.Matched(values) 159 | case None => ValuePattern.NotMatched(s"Input '$in' does not match regex '${regex.regex}'") 160 | } 161 | } 162 | 163 | object ManyP extends ValuePattern[Seq[String], Seq[String]] { 164 | override def apply(in: Seq[String]): ValuePattern.Match[Seq[String]] = ValuePattern.Matched(in) 165 | } 166 | 167 | object NonEmptyP extends ValuePattern[Seq[String], ParamValues] { 168 | override def apply(in: Seq[String]): ValuePattern.Match[ParamValues] = in.headOption match { 169 | case Some(value) => ValuePattern.Matched(ParamValues(value, in.tail: _*)) 170 | case None => ValuePattern.NotMatched("No value") 171 | } 172 | } 173 | 174 | object SingleP extends ValuePattern[Seq[String], String] { 175 | override def apply(in: Seq[String]): ValuePattern.Match[String] = in.lastOption match { 176 | case Some(value) => ValuePattern.Matched(value) 177 | case None => ValuePattern.NotMatched("No value") 178 | } 179 | } 180 | 181 | object OptionalP extends ValuePattern[Seq[String], Option[String]] { 182 | override def apply(in: Seq[String]): ValuePattern.Match[Option[String]] = ValuePattern.Matched(in.lastOption) 183 | } 184 | -------------------------------------------------------------------------------- /core/src/main/scala/com/github/mvv/routineer/syntax/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2012, 2019 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer.syntax 18 | 19 | import com.github.mvv.routineer.{Args, EqualsP, RoutePattern, ValueCheck, ValuePattern} 20 | 21 | import scala.language.implicitConversions 22 | 23 | /** Essential implicit convertions. */ 24 | trait RoutineerImplicits { 25 | implicit def stringToValueCheck(str: String): ValueCheck[String] = 26 | EqualsP(str).check 27 | implicit def stringToRoutePattern(str: String): RoutePattern.PartialPath[Args._0] = 28 | RoutePattern.Root /> EqualsP(str).check 29 | implicit def checkToRoutePattern[R](check: ValueCheck[String]): RoutePattern.PartialPath[Args._0] = 30 | RoutePattern.Root /> check 31 | implicit def forceCheckToRoutePattern[R](check: ValueCheck.Force[String]): RoutePattern.PartialPath[Args._0] = 32 | RoutePattern.Root /> check 33 | implicit def patternToRoutePattern[R](pattern: ValuePattern[String, R]): RoutePattern.PartialPath[Args._1[R]] = 34 | RoutePattern.Root /> pattern 35 | implicit def forcePatternToRoutePattern[R]( 36 | pattern: ValuePattern.Force[String, R] 37 | ): RoutePattern.PartialPath[Args._1[R]] = 38 | RoutePattern.Root /> pattern 39 | } 40 | 41 | trait RoutineerSyntax extends RoutineerImplicits { 42 | val Root: RoutePattern.PartialPath[Args._0] = RoutePattern.Root 43 | val * : ValuePattern[String, String] = ValuePattern.id[String] 44 | } 45 | 46 | object `package` extends RoutineerSyntax 47 | -------------------------------------------------------------------------------- /core/src/test/scala/com/github/mvv/routineer/tests/AppendSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2011 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer.tests 18 | 19 | import com.github.mvv.routineer._ 20 | import org.specs2.mutable._ 21 | 22 | object AppendSpec extends Specification { 23 | /* 24 | "Appending a route to itself must raise an error" in { 25 | val rs = Routes[Any, Any](PathSpec.empty when (r => r)) 26 | (rs ++ rs) must throwAn[RouteOvershadowedException[_, _]] 27 | } 28 | */ 29 | } 30 | -------------------------------------------------------------------------------- /core/src/test/scala/com/github/mvv/routineer/tests/RoutesSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2011 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer.tests 18 | 19 | import com.github.mvv.routineer.{Args, Dispatch, Route, Routes} 20 | import com.github.mvv.routineer.syntax._ 21 | import org.specs2.mutable.Specification 22 | 23 | class RoutesSpec extends Specification { 24 | "One-segment echo route" >> { 25 | val rs = Routes.forHandler[Route.Handler.Apply[String]#H](Route.handle(*) { segment => 26 | segment 27 | }) 28 | rs.dispatch(Seq("foo")) must beLike { 29 | case r: Dispatch.Handler[_, Route.Handler.Apply[String]#H] => 30 | Args.apply(r.handler, r.args) mustEqual "foo" 31 | } 32 | } 33 | 34 | "One-segment echo route with env" >> { 35 | val rs = Routes.forHandler[Route.WithEnv.Apply[Int, String]#H](Route.withEnv[Int](*) { (env, segment) => 36 | s"$env:$segment" 37 | }) 38 | rs.dispatch(Seq("foo")) must beLike { 39 | case r: Dispatch.Handler[_, Route.WithEnv.Apply[Int, String]#H] => 40 | Args.apply(r.handler.handler, r.handler.growable(r.args).prepend(123)) mustEqual "123:foo" 41 | } 42 | } 43 | 44 | /* 45 | "Empty route set" in { 46 | val rs = Routes[Any, Any]() 47 | rs((), "") must_== None 48 | rs((), "/") must_== None 49 | rs((), "aaaa") must_== None 50 | rs((), "/aaaa") must_== None 51 | rs((), "/aaaa/bbbb") must_== None 52 | rs((), "/aaaa/bbbb/") must_== None 53 | } 54 | 55 | "Empty path" in { 56 | val rs = Routes[Any, Any](PathSpec.empty when (r => r)) 57 | rs(1, "").map(_.apply) must_== Some(1) 58 | rs(2, "/").map(_.apply) must_== Some(2) 59 | rs((), "/aaaa") must_== None 60 | rs((), "/aaaa/bbbb") must_== None 61 | } 62 | 63 | "Const path" in { 64 | val rs = Routes[Any, Any]("a" whenDo (r => r)) 65 | rs((), "") must_== None 66 | rs((), "a").isDefined must_== true 67 | rs((), "/a").isDefined must_== true 68 | rs((), "/a/").isDefined must_== true 69 | rs((), "/a/b").isDefined must_== false 70 | } 71 | 72 | "Star pattern" in { 73 | val rs1 = Routes[Any, String]("a" /> * when ((_: Any, s) => s)) 74 | rs1((), "") must_== None 75 | rs1((), "/") must_== None 76 | rs1((), "/a") must_== None 77 | rs1((), "/aaaa") must_== None 78 | rs1((), "/a/bbbb").map(_.apply) must_== Some("bbbb") 79 | val rs2 = Routes[Any, String]("a" /> * /> * when ((_: Any, s1, s2) => s1 + s2)) 80 | rs2((), "") must_== None 81 | rs2((), "/") must_== None 82 | rs2((), "/a/bbbb") must_== None 83 | rs2((), "/a/bbbb/") must_== None 84 | rs2((), "/a/bbbb/cccc").map(_.apply) must_== Some("bbbbcccc") 85 | rs2((), "/a/bbbb/cccc/d") must_== None 86 | val rs3 = Routes[Any, String](* when ((_: Any, s) => s)) 87 | rs3((), "") must_== None 88 | rs3((), "aaaa").map(_.apply) must_== Some("aaaa") 89 | rs3((), "/aaaa").map(_.apply) must_== Some("aaaa") 90 | rs3((), "/aaaa/").map(_.apply) must_== Some("aaaa") 91 | rs3((), "/aaaa/bbbb") must_== None 92 | val rs4 = Routes[Any, String](* /> "b" when ((_: Any, s) => s)) 93 | rs4((), "") must_== None 94 | rs4((), "/") must_== None 95 | rs4((), "/b") must_== None 96 | rs4((), "/aaaa") must_== None 97 | rs4((), "/aaaa/b").map(_.apply) must_== Some("aaaa") 98 | } 99 | 100 | "Maximum path spec length" in { 101 | val rs0 = Routes[Any, String](PathSpec.empty when (_ => "")) 102 | rs0((), "").map(_.apply) must_== Some("") 103 | val rs1 = Routes[Any, String](* when ((_: Any, s1) => s1)) 104 | rs1((), "a").map(_.apply) must_== Some("a") 105 | val rs2 = Routes[Any, String](* /> * when ((_: Any, s1, s2) => s1 + s2)) 106 | rs2((), "a/b").map(_.apply) must_== Some("ab") 107 | val rs3 = Routes[Any, String](* /> * /> * when ((_: Any, s1, s2, s3) => s1 + s2 + s3)) 108 | rs3((), "a/b/c").map(_.apply) must_== Some("abc") 109 | val rs4 = Routes[Any, String](* /> * /> * /> * when { (_: Any, s1, s2, s3, s4) => 110 | s1 + s2 + s3 + s4 111 | }) 112 | rs4((), "a/b/c/d").map(_.apply) must_== Some("abcd") 113 | val rs5 = Routes[Any, String](* /> * /> * /> * /> * when { (_: Any, s1, s2, s3, s4, s5) => 114 | s1 + s2 + s3 + s4 + s5 115 | }) 116 | rs5((), "a/b/c/d/e").map(_.apply) must_== Some("abcde") 117 | val rs6 = Routes[Any, String](* /> * /> * /> * /> * /> * when { (_: Any, s1, s2, s3, s4, s5, s6) => 118 | s1 + s2 + s3 + s4 + s5 + s6 119 | }) 120 | rs6((), "a/b/c/d/e/f").map(_.apply) must_== Some("abcdef") 121 | val rs7 = Routes[Any, String](* /> * /> * /> * /> * /> * /> * when { (_: Any, s1, s2, s3, s4, s5, s6, s7) => 122 | s1 + s2 + s3 + s4 + s5 + s6 + s7 123 | }) 124 | rs7((), "a/b/c/d/e/f/g").map(_.apply) must_== Some("abcdefg") 125 | } 126 | */ 127 | } 128 | -------------------------------------------------------------------------------- /examples/servlet/src/main/scala/com/github/mvv/routineer/examples/servlet/ExampleServlet.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Mikhail Vorozhtsov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.mvv.routineer.examples.servlet 18 | 19 | import javax.servlet.http._ 20 | import com.github.mvv.routineer._ 21 | 22 | /* 23 | trait Handler extends DeclRouteMap[String, HttpServletRequest, String] { 24 | final protected def get() = routeDecl("GET", PathSpec.empty) 25 | final protected def get[E <: PathSpec.Elems](spec: PathSpec[E]) = 26 | routeDecl("GET", spec) 27 | final protected def post() = routeDecl("POST", PathSpec.empty) 28 | final protected def post[E <: PathSpec.Elems](spec: PathSpec[E]) = 29 | routeDecl("POST", spec) 30 | } 31 | 32 | object EchoHandler extends Handler { 33 | get("echo" /> *) { (_, str) => 34 | str 35 | } 36 | get("echo-rest" /# *) { (_, str) => 37 | str 38 | } 39 | get("echo-int" /> IntP) { (_, i) => 40 | i.toString 41 | } 42 | } 43 | 44 | object CondHandler extends Handler { 45 | get(* /> (IntP >>> PositiveP[Int])).onlyIf { (_, str, len) => 46 | str.length <= len 47 | } { (req, str, len) => 48 | "%s: string \"%s\" has length <= %d" format (req.getRequestURI, str, len) 49 | } 50 | } 51 | 52 | object GuardHandler extends Handler { 53 | get(*).guard { (_, str) => 54 | Some(str.count(_ == 'a')).filter(_ > 0) 55 | } { (req, str, as) => 56 | "%s: string \"%s\" has %d 'a'(s)" format (req.getRequestURI, str, as) 57 | } 58 | } 59 | */ 60 | 61 | class ExampleServlet extends HttpServlet { 62 | /* 63 | lazy val routes = "cond" /: { 64 | CondHandler ++ 65 | GuardHandler 66 | } ++ 67 | EchoHandler 68 | */ 69 | 70 | override protected def service(req: HttpServletRequest, resp: HttpServletResponse): Unit = () 71 | /* 72 | routes(req, req.getMethod, req.getRequestURI) match { 73 | case Some(code) => 74 | resp.setContentType("text/plain") 75 | resp.setCharacterEncoding("UTF-8") 76 | resp.getWriter.append(code()).close() 77 | case None => 78 | resp.sendError(HttpServletResponse.SC_NOT_FOUND) 79 | } 80 | */ 81 | } 82 | -------------------------------------------------------------------------------- /examples/servlet/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | ExampleServlet 10 | com.github.mvv.routineer.examples.servlet.ExampleServlet 11 | 12 | 13 | ExampleServlet 14 | /* 15 | 16 | 17 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.3.3 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.6") 2 | addSbtPlugin("io.crashbox" % "sbt-gpg" % "0.2.0") 3 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.8") 4 | addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "4.0.2") 5 | -------------------------------------------------------------------------------- /project/secrets.tar.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvv/routineer/d32bd06ed3785924214f7f43ee67bd748dbf33e9/project/secrets.tar.enc --------------------------------------------------------------------------------