aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala
blob: 07e32b0738ecaff2d038f9474806e8ee135a9320 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package xyz.driver.core
package rest
package directives

import java.time.Instant
import java.util.UUID

import akka.http.scaladsl.model.Uri.Path
import akka.http.scaladsl.server.PathMatcher.{Matched, Unmatched}
import akka.http.scaladsl.server.{PathMatcher, PathMatcher1, PathMatchers => AkkaPathMatchers}
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.refineV
import xyz.driver.core.time.Time

import scala.util.control.NonFatal

/** Akka-HTTP path matchers for suctom core types */
trait PathMatchers {

  private def UuidInPath[T]: PathMatcher1[Id[T]] =
    AkkaPathMatchers.JavaUUID.map((id: UUID) => Id[T](id.toString.toLowerCase))

  def IdInPath[T]: PathMatcher1[Id[T]] = UuidInPath[T] | new PathMatcher1[Id[T]] {
    def apply(path: Path) = path match {
      case Path.Segment(segment, tail) => Matched(tail, Tuple1(Id[T](segment)))
      case _                           => Unmatched
    }
  }

  def NameInPath[T]: PathMatcher1[Name[T]] = new PathMatcher1[Name[T]] {
    def apply(path: Path) = path match {
      case Path.Segment(segment, tail) => Matched(tail, Tuple1(Name[T](segment)))
      case _                           => Unmatched
    }
  }

  private def timestampInPath: PathMatcher1[Long] =
    PathMatcher("""[+-]?\d*""".r) flatMap { string =>
      try Some(string.toLong)
      catch { case _: IllegalArgumentException => None }
    }

  def InstantInPath: PathMatcher1[Instant] =
    new PathMatcher1[Instant] {
      def apply(path: Path): PathMatcher.Matching[Tuple1[Instant]] = path match {
        case Path.Segment(head, tail) =>
          try Matched(tail, Tuple1(Instant.parse(head)))
          catch {
            case NonFatal(_) => Unmatched
          }
        case _ => Unmatched
      }
    } | timestampInPath.map(Instant.ofEpochMilli)

  def TimeInPath: PathMatcher1[Time] = InstantInPath.map(instant => Time(instant.toEpochMilli))

  def NonEmptyNameInPath[T]: PathMatcher1[NonEmptyName[T]] = new PathMatcher1[NonEmptyName[T]] {
    def apply(path: Path) = path match {
      case Path.Segment(segment, tail) =>
        refineV[NonEmpty](segment) match {
          case Left(_)               => Unmatched
          case Right(nonEmptyString) => Matched(tail, Tuple1(NonEmptyName[T](nonEmptyString)))
        }
      case _ => Unmatched
    }
  }

  def RevisionInPath[T]: PathMatcher1[Revision[T]] =
    PathMatcher("""[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}""".r) flatMap { string =>
      Some(Revision[T](string))
    }

}