aboutsummaryrefslogtreecommitdiff
path: root/core-rest/src/main/scala/xyz/driver/core/generators.scala
blob: d00b6dd0b6d67d8b12194ee025c9098b219510f8 (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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package xyz.driver.core

import enumeratum._
import java.math.MathContext
import java.time.{Instant, LocalDate, ZoneOffset}
import java.util.UUID

import xyz.driver.core.time.{Time, TimeOfDay, TimeRange}
import xyz.driver.core.date.{Date, DayOfWeek}

import scala.reflect.ClassTag
import scala.util.Random
import eu.timepit.refined.refineV
import eu.timepit.refined.api.Refined
import eu.timepit.refined.collection._

object generators {

  private val random = new Random
  import random._
  private val secureRandom = new java.security.SecureRandom()

  private val DefaultMaxLength       = 10
  private val StringLetters          = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ".toSet
  private val NonAmbigiousCharacters = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
  private val Numbers                = "0123456789"

  private def nextTokenString(length: Int, chars: IndexedSeq[Char]): String = {
    val builder = new StringBuilder
    for (_ <- 0 until length) {
      builder += chars(secureRandom.nextInt(chars.length))
    }
    builder.result()
  }

  /** Creates a random invitation token.
    *
    * This token is meant fo human input and avoids using ambiguous characters such as 'O' and '0'. It
    * therefore contains less entropy and is not meant to be used as a cryptographic secret. */
  @deprecated(
    "The term 'token' is too generic and security and readability conventions are not well defined. " +
      "Services should implement their own version that suits their security requirements.",
    "1.11.0"
  )
  def nextToken(length: Int): String = nextTokenString(length, NonAmbigiousCharacters)

  @deprecated(
    "The term 'token' is too generic and security and readability conventions are not well defined. " +
      "Services should implement their own version that suits their security requirements.",
    "1.11.0"
  )
  def nextNumericToken(length: Int): String = nextTokenString(length, Numbers)

  def nextInt(maxValue: Int, minValue: Int = 0): Int = random.nextInt(maxValue - minValue) + minValue

  def nextBoolean(): Boolean = random.nextBoolean()

  def nextDouble(): Double = random.nextDouble()

  def nextId[T](): Id[T] = Id[T](nextUuid().toString)

  def nextId[T](maxLength: Int): Id[T] = Id[T](nextString(maxLength))

  def nextNumericId[T](): Id[T] = Id[T](nextLong.abs.toString)

  def nextNumericId[T](maxValue: Int): Id[T] = Id[T](nextInt(maxValue).toString)

  def nextName[T](maxLength: Int = DefaultMaxLength): Name[T] = Name[T](nextString(maxLength))

  def nextNonEmptyName[T](maxLength: Int = DefaultMaxLength): NonEmptyName[T] =
    NonEmptyName[T](nextNonEmptyString(maxLength))

  def nextUuid(): UUID = java.util.UUID.randomUUID

  def nextRevision[T](): Revision[T] = Revision[T](nextUuid().toString)

  def nextString(maxLength: Int = DefaultMaxLength): String =
    (oneOf[Char](StringLetters) +: arrayOf(oneOf[Char](StringLetters), maxLength - 1)).mkString

  def nextNonEmptyString(maxLength: Int = DefaultMaxLength): String Refined NonEmpty = {
    refineV[NonEmpty](
      (oneOf[Char](StringLetters) +: arrayOf(oneOf[Char](StringLetters), maxLength - 1)).mkString
    ).right.get
  }

  def nextOption[T](value: => T): Option[T] = if (nextBoolean()) Option(value) else None

  def nextPair[L, R](left: => L, right: => R): (L, R) = (left, right)

  def nextTriad[F, S, T](first: => F, second: => S, third: => T): (F, S, T) = (first, second, third)

  def nextInstant(): Instant = Instant.ofEpochMilli(math.abs(nextLong() % System.currentTimeMillis))

  def nextTime(): Time = nextInstant()

  def nextTimeOfDay: TimeOfDay = TimeOfDay(java.time.LocalTime.MIN.plusSeconds(nextLong), java.util.TimeZone.getDefault)

  def nextTimeRange(): TimeRange = {
    val oneTime     = nextTime()
    val anotherTime = nextTime()

    TimeRange(
      Time(scala.math.min(oneTime.millis, anotherTime.millis)),
      Time(scala.math.max(oneTime.millis, anotherTime.millis)))
  }

  def nextDate(): Date = nextTime().toDate(java.util.TimeZone.getTimeZone("UTC"))

  def nextLocalDate(): LocalDate = nextInstant().atZone(ZoneOffset.UTC).toLocalDate

  def nextDayOfWeek(): DayOfWeek = oneOf(DayOfWeek.All)

  def nextBigDecimal(multiplier: Double = 1000000.00, precision: Int = 2): BigDecimal =
    BigDecimal(multiplier * nextDouble, new MathContext(precision))

  def oneOf[T](items: T*): T = oneOf(items.toSet)

  def oneOf[T](items: Set[T]): T = items.toSeq(nextInt(items.size))

  def oneOf[T <: EnumEntry](enum: Enum[T]): T = oneOf(enum.values: _*)

  def arrayOf[T: ClassTag](generator: => T, maxLength: Int = DefaultMaxLength, minLength: Int = 0): Array[T] =
    Array.fill(nextInt(maxLength, minLength))(generator)

  def seqOf[T](generator: => T, maxLength: Int = DefaultMaxLength, minLength: Int = 0): Seq[T] =
    Seq.fill(nextInt(maxLength, minLength))(generator)

  def vectorOf[T](generator: => T, maxLength: Int = DefaultMaxLength, minLength: Int = 0): Vector[T] =
    Vector.fill(nextInt(maxLength, minLength))(generator)

  def listOf[T](generator: => T, maxLength: Int = DefaultMaxLength, minLength: Int = 0): List[T] =
    List.fill(nextInt(maxLength, minLength))(generator)

  def setOf[T](generator: => T, maxLength: Int = DefaultMaxLength, minLength: Int = 0): Set[T] =
    seqOf(generator, maxLength, minLength).toSet

  def mapOf[K, V](
      keyGenerator: => K,
      valueGenerator: => V,
      maxLength: Int = DefaultMaxLength,
      minLength: Int = 0): Map[K, V] =
    seqOf(nextPair(keyGenerator, valueGenerator), maxLength, minLength).toMap
}