Skip to content

Commit

Permalink
More use friendly syntaxe
Browse files Browse the repository at this point in the history
  • Loading branch information
loicknuchel committed Oct 31, 2020
1 parent 0140a9a commit c5b841b
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 77 deletions.
15 changes: 15 additions & 0 deletions src/main/scala/fr/loicknuchel/safeql/gen/Generator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,19 @@ object Generator {
database <- reader.read()
_ <- writer.write(database).toIO
} yield ()

/**
* Allow to start with writer
*/

def writer(writer: Writer) = new Builder(writer)

class Builder(writer: Writer) {
def flyway(flywayLocations: String*): FlywayGenerator = Generator.flyway(flywayLocations: _*).writer(writer)

def fromFiles(paths: List[String]): SQLFilesGenerator = Generator.fromFiles(paths).writer(writer)

def reader(reader: Reader): ReaderGenerator = Generator.reader(reader).writer(writer)
}

}
16 changes: 12 additions & 4 deletions src/main/scala/fr/loicknuchel/safeql/gen/reader/H2Reader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,29 @@ import scala.concurrent.ExecutionContext
class H2Reader(val url: String,
val user: String,
val pass: String,
schema: Option[String],
excludes: Option[String]) extends Reader {
val schema: Option[String],
val excludes: Option[String]) extends Reader {
val driver: String = "org.h2.Driver"
protected[gen] lazy val xa: doobie.Transactor[IO] = {
implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
Transactor.fromDriverManager[IO](driver, url, user, pass)
}

def url(u: String): H2Reader = new H2Reader(u, user, pass, schema, excludes)

def user(u: String): H2Reader = new H2Reader(url, u, pass, schema, excludes)

def pass(p: String): H2Reader = new H2Reader(url, user, p, schema, excludes)

def schema(s: String): H2Reader = new H2Reader(url, user, pass, schema = Some(s), excludes)

def excludes(regex: String): H2Reader = new H2Reader(url, user, pass, schema, excludes = Some(regex))

override def read(): IO[Database] = for {
columns <- readColumns(xa)
crossReferences <- readCrossReferences(xa)
} yield buildDatabase(columns, crossReferences)

def excludes(regex: String): H2Reader = new H2Reader(url, user, pass, schema, excludes = Some(regex))

protected def buildDatabase(columns: List[Column], crossReferences: List[CrossReference]): Database = {
val refs = crossReferences.map(r => (Database.FieldRef(r.FKTABLE_SCHEMA, r.FKTABLE_NAME, r.FKCOLUMN_NAME), Database.FieldRef(r.PKTABLE_SCHEMA, r.PKTABLE_NAME, r.PKCOLUMN_NAME))).toMap
Database(columns.groupBy(_.TABLE_SCHEMA).toList.sortBy(_._1).map { case (schema, tables) =>
Expand Down
18 changes: 12 additions & 6 deletions src/main/scala/fr/loicknuchel/safeql/gen/writer/ScalaWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@ import fr.loicknuchel.safeql.gen.writer.ScalaWriter.{DatabaseConfig, TableConfig
import fr.loicknuchel.safeql.gen.writer.Writer.IdentifierStrategy
import fr.loicknuchel.safeql.utils.StringUtils

class ScalaWriter(directory: String,
packageName: String,
identifierStrategy: IdentifierStrategy,
config: DatabaseConfig) extends Writer {
class ScalaWriter(val directory: String,
val packageName: String,
val identifierStrategy: IdentifierStrategy,
val config: DatabaseConfig) extends Writer {
require(config.getConfigErrors.isEmpty, s"DatabaseConfig has some errors :${config.getConfigErrors.map("\n - " + _).mkString}")
require(StringUtils.isScalaPackage(packageName), s"'$packageName' is an invalid scala package name")

def directory(dir: String): ScalaWriter = new ScalaWriter(dir, packageName, identifierStrategy, config)

override protected def getDatabaseErrors(db: Database): List[String] = config.getDatabaseErrors(db)
def packageName(pkg: String): ScalaWriter = new ScalaWriter(directory, pkg, identifierStrategy, config)

override protected[gen] def rootFolderPath: String = directory + "/" + packageName.replaceAll("\\.", "/")
def identifierStrategy(idf: IdentifierStrategy): ScalaWriter = new ScalaWriter(directory, packageName, idf, config)

def config(conf: DatabaseConfig): ScalaWriter = new ScalaWriter(directory, packageName, identifierStrategy, conf)

override def getDatabaseErrors(db: Database): List[String] = config.getDatabaseErrors(db)

override def rootFolderPath: String = directory + "/" + packageName.replaceAll("\\.", "/")

override protected[writer] def tableFilePath(t: Table): String = tablesFolderPath + "/" + idf(t.name) + ".scala"

Expand Down
4 changes: 4 additions & 0 deletions src/main/scala/fr/loicknuchel/safeql/models/Page.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ object Page {
def filters(f: Map[String, String]): Params = copy(filters = f)

def filters(f: (String, String)*): Params = filters(f.toMap)

def withNullsFirst: Params = copy(nullsFirst = true)

def withNullsLast: Params = copy(nullsFirst = false)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import scala.collection.mutable
import scala.util.control.NonFatal
import scala.util.{Failure, Success, Try}

object Extensions {
private[safeql] object Extensions {

implicit class RichOption[A](val in: Option[A]) extends AnyVal {
def toEither[E](e: => E): Either[E, A] = in match {
Expand Down
11 changes: 10 additions & 1 deletion src/main/scala/fr/loicknuchel/safeql/utils/FileUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package fr.loicknuchel.safeql.utils
import java.io.File
import java.nio.file.{Files, Paths}

import fr.loicknuchel.safeql.utils.Extensions._

import scala.jdk.CollectionConverters._
import scala.util.Try

object FileUtils {
private[safeql] object FileUtils {
def parent(path: String): String =
path.split("/").dropRight(1).mkString("/")

Expand All @@ -22,6 +24,13 @@ object FileUtils {
listDir(new File(path)).filter(_.isFile).map(_.getPath).sorted
}

// return a map for files with their relative path inside the directory and their content
def getDirContent(path: String): Try[Map[String, String]] = {
FileUtils.listFiles(path)
.flatMap(_.map(p => FileUtils.read(p).map(c => (p.stripPrefix(path + "/"), c))).sequence)
.map(_.toMap)
}

def read(path: String): Try[String] =
Try(Files.readAllLines(Paths.get(path))).map(_.asScala.mkString("\n"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package fr.loicknuchel.safeql.utils

import java.text.Normalizer

object StringUtils {
private[safeql] object StringUtils {
def removeDiacritics(str: String): String =
Normalizer.normalize(str, Normalizer.Form.NFD)
.replaceAll("\\p{InCombiningDiacriticalMarks}+", "")
Expand Down
60 changes: 15 additions & 45 deletions src/test/scala/fr/loicknuchel/safeql/gen/GeneratorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,52 @@ package fr.loicknuchel.safeql.gen

import java.util.UUID

import cats.data.NonEmptyList
import fr.loicknuchel.safeql.gen.reader.H2Reader
import fr.loicknuchel.safeql.gen.writer.ScalaWriter.{DatabaseConfig, FieldConfig, SchemaConfig, TableConfig}
import fr.loicknuchel.safeql.gen.writer.{ScalaWriter, Writer}
import fr.loicknuchel.safeql.testingutils.BaseSpec
import fr.loicknuchel.safeql.utils.Extensions._
import fr.loicknuchel.safeql.testingutils.{BaseSpec, CLI}
import fr.loicknuchel.safeql.utils.FileUtils
import org.flywaydb.core.Flyway
import org.flywaydb.core.internal.jdbc.DriverDataSource
import org.scalatest.BeforeAndAfterEach

import scala.util.Try

class GeneratorSpec extends BaseSpec with BeforeAndAfterEach {
private val root = "target/tmp-generator-tests"
private val reader = H2Reader(
url = s"jdbc:h2:mem:${UUID.randomUUID()};MODE=PostgreSQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1",
schema = Some("PUBLIC"),
excludes = Some(".*flyway.*"))
private val writer = ScalaWriter(
directory = "src/test/scala",
packageName = "fr.loicknuchel.safeql.testingutils.database",
identifierStrategy = Writer.IdentifierStrategy.upperCase,
config = DatabaseConfig(
scaladoc = _ => Some("Hello"),
imports = List("fr.loicknuchel.safeql.testingutils.Entities._"),
schemas = Map("PUBLIC" -> SchemaConfig(tables = Map(
"users" -> TableConfig(alias = Some("u"), fields = Map(
"id" -> FieldConfig(customType = Some("User.Id")))),
"categories" -> TableConfig(alias = "c", sort = TableConfig.Sort("name", NonEmptyList.of("-name", "id")), search = List("name"), fields = Map(
"id" -> FieldConfig(customType = Some("Category.Id")))),
"posts" -> TableConfig(alias = Some("p"), fields = Map(
"id" -> FieldConfig(customType = Some("Post.Id"))))
)))))
private val root = "target/tests-generator"

override protected def afterEach(): Unit = FileUtils.delete(root).get

describe("Generator") {
it("should generate the same files with all the generators") {
// Basic generation
val reader = H2Reader(
url = s"jdbc:h2:mem:${UUID.randomUUID()};MODE=PostgreSQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1",
schema = Some("PUBLIC"),
excludes = Some(".*flyway.*"))
Flyway.configure()
.dataSource(new DriverDataSource(this.getClass.getClassLoader, reader.driver, reader.url, reader.user, reader.pass))
.locations("classpath:sql_migrations")
.load().migrate()
val basicPath = s"$root/basic-gen"
Generator.reader(reader).writer(writer.directory(basicPath)).generate().unsafeRunSync()
val basicDb = getFolderContent(basicPath).get
Generator.reader(reader).writer(CLI.GenerateSampleDatabase.writer.directory(basicPath)).generate().unsafeRunSync()
val basicDb = FileUtils.getDirContent(basicPath).get

// Flyway generator
val flywapPath = s"$root/flyway-gen"
Generator.flyway("classpath:sql_migrations").writer(writer.directory(flywapPath)).generate().unsafeRunSync()
val flywayDb = getFolderContent(flywapPath).get
Generator.flyway("classpath:sql_migrations").writer(CLI.GenerateSampleDatabase.writer.directory(flywapPath)).generate().unsafeRunSync()
val flywayDb = FileUtils.getDirContent(flywapPath).get
flywayDb shouldBe basicDb

// SQL files generator
val sqlFilesPath = s"$root/sql-gen"
Generator.fromFiles(List("src/test/resources/sql_migrations/V1__test_schema.sql")).writer(writer.directory(sqlFilesPath)).generate().unsafeRunSync()
val sqlFilesDb = getFolderContent(sqlFilesPath).get
Generator.fromFiles(List("src/test/resources/sql_migrations/V1__test_schema.sql")).writer(CLI.GenerateSampleDatabase.writer.directory(sqlFilesPath)).generate().unsafeRunSync()
val sqlFilesDb = FileUtils.getDirContent(sqlFilesPath).get
sqlFilesDb shouldBe basicDb
}
it("should keep the generated database up to date") {
val flywayWriter = writer.directory(s"$root/flyway-gen")
val flywayWriter = CLI.GenerateSampleDatabase.writer.directory(s"$root/flyway-gen")
Generator.flyway("classpath:sql_migrations").writer(flywayWriter).generate().unsafeRunSync()

val flywayDb = getFolderContent(flywayWriter.rootFolderPath).get
val currentDb = getFolderContent(writer.rootFolderPath).get
val flywayDb = FileUtils.getDirContent(flywayWriter.rootFolderPath).get
val currentDb = FileUtils.getDirContent(CLI.GenerateSampleDatabase.writer.rootFolderPath).get
currentDb shouldBe flywayDb
}
ignore("should generate the database tables") { // run this test to generate the test database tables
Generator.reader(reader).writer(writer).generate().unsafeRunSync()
}
}

private def getFolderContent(path: String): Try[Map[String, String]] = {
FileUtils.listFiles(path)
.flatMap(_.map(p => FileUtils.read(p).map(c => (p.stripPrefix(path), c))).sequence)
.map(_.toMap)
}
}
20 changes: 20 additions & 0 deletions src/test/scala/fr/loicknuchel/safeql/models/ExceptionsSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package fr.loicknuchel.safeql.models

import fr.loicknuchel.safeql.testingutils.BaseSpec

class ExceptionsSpec extends BaseSpec {
private val e1 = new Exception("an error")
private val e2 = new Exception("an other error")
private val m = MultiException(e1, e2)

describe("Exceptions") {
describe("MultiException") {
it("should carry multiple exceptions") {
m.getMessage shouldBe "\n - an error\n - an other error"
m.getLocalizedMessage shouldBe "\n - an error\n - an other error"
m.getStackTrace shouldBe e1.getStackTrace
m.getCause shouldBe e1.getCause
}
}
}
}

This file was deleted.

43 changes: 43 additions & 0 deletions src/test/scala/fr/loicknuchel/safeql/models/PageSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package fr.loicknuchel.safeql.models

import fr.loicknuchel.safeql.testingutils.BaseSpec

class PageSpec extends BaseSpec {
describe("Page") {
describe("Params") {
it("should have all default values") {
Page.Params().page shouldBe 1 // testing the empty constructor
}
it("should have setters") {
val p = Page.Params()

p.page shouldBe 1
p.page(2).page shouldBe 2

p.pageSize shouldBe 20
p.pageSize(2).pageSize shouldBe 2

p.search shouldBe None
p.search("q").search shouldBe Some("q")

p.orderBy shouldBe List()
p.orderBy("name,date", "score").orderBy shouldBe List("name", "date", "score")

p.filters shouldBe Map()
p.filters("type" -> "meetup", "future" -> "true").filters shouldBe Map("type" -> "meetup", "future" -> "true")

p.nullsFirst shouldBe false
p.withNullsFirst.nullsFirst shouldBe true
}
it("should clean arguments on orderBy setter") {
val p = Page.Params()
p.orderBy("a,,b ,c", "d,e").orderBy shouldBe List("a", "b", "c", "d", "e")
}
it("should update order by only when empty") {
val p = Page.Params()
p.defaultOrderBy("b").orderBy shouldBe List("b")
p.orderBy("a").defaultOrderBy("b").orderBy shouldBe List("a")
}
}
}
}
37 changes: 37 additions & 0 deletions src/test/scala/fr/loicknuchel/safeql/testingutils/CLI.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package fr.loicknuchel.safeql.testingutils

import cats.data.NonEmptyList
import cats.effect.IO
import fr.loicknuchel.safeql.gen.Generator
import fr.loicknuchel.safeql.gen.writer.ScalaWriter.{DatabaseConfig, FieldConfig, SchemaConfig, TableConfig}
import fr.loicknuchel.safeql.gen.writer.{ScalaWriter, Writer}

object CLI {
def main(args: Array[String]): Unit = {
GenerateSampleDatabase.run().unsafeRunSync()
println("Done")
}

object GenerateSampleDatabase {
val writer: ScalaWriter = ScalaWriter(
directory = "src/test/scala",
packageName = "fr.loicknuchel.safeql.testingutils.database",
identifierStrategy = Writer.IdentifierStrategy.upperCase,
config = DatabaseConfig(
scaladoc = _ => Some("Hello"),
imports = List("fr.loicknuchel.safeql.testingutils.Entities._"),
schemas = Map("PUBLIC" -> SchemaConfig(tables = Map(
"users" -> TableConfig(alias = Some("u"), fields = Map(
"id" -> FieldConfig(customType = Some("User.Id")))),
"categories" -> TableConfig(alias = "c", sort = TableConfig.Sort("name", NonEmptyList.of("-name", "id")), search = List("name"), fields = Map(
"id" -> FieldConfig(customType = Some("Category.Id")))),
"posts" -> TableConfig(alias = Some("p"), fields = Map(
"id" -> FieldConfig(customType = Some("Post.Id"))))
)))))

def run(): IO[Unit] = {
Generator.flyway("classpath:sql_migrations").writer(writer).generate()
}
}

}
11 changes: 10 additions & 1 deletion src/test/scala/fr/loicknuchel/safeql/utils/FileUtilsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import fr.loicknuchel.safeql.testingutils.BaseSpec
import org.scalatest.BeforeAndAfterEach

class FileUtilsSpec extends BaseSpec with BeforeAndAfterEach {
private val root = "target/file-utils-tests"
private val root = "target/tests-file-utils"

override protected def beforeEach(): Unit = FileUtils.mkdirs(root).get

Expand Down Expand Up @@ -35,5 +35,14 @@ class FileUtilsSpec extends BaseSpec with BeforeAndAfterEach {
FileUtils.delete(s"$root/src").get
an[Exception] should be thrownBy FileUtils.read(s"$root/src/test/scala/fr/lkn/main.scala").get
}
it("should list folder content") {
FileUtils.mkdirs(s"$root/src/main/scala/fr/loicknuchel").get
FileUtils.write(s"$root/src/main/scala/fr/loicknuchel/Main.scala", "public class Main").get
FileUtils.write(s"$root/src/main/scala/README.md", "The readme").get

FileUtils.getDirContent(s"$root/src/main/scala").get shouldBe Map(
"README.md" -> "The readme",
"fr/loicknuchel/Main.scala" -> "public class Main")
}
}
}

0 comments on commit c5b841b

Please sign in to comment.