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