Skip to content

Commit

Permalink
Pre-0.2.x imports API changes
Browse files Browse the repository at this point in the history
  • Loading branch information
travisbrown committed Apr 20, 2020
1 parent 88676ce commit cce9bd7
Show file tree
Hide file tree
Showing 12 changed files with 90 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ object Canonicalization {
case _ => canonicalize(child)
}

case class LocalFile(dirs: LocalDirs, filename: String) {
private case class LocalFile(dirs: LocalDirs, filename: String) {
def toPath: Path = {
def toPath(l: List[String]) = "/" + l.intercalate("/")

Expand All @@ -76,7 +76,7 @@ object Canonicalization {
def canonicalize: LocalFile = LocalFile.canonicalize(this)
}

object LocalFile {
private object LocalFile {
def apply[F[_]](path: Path)(implicit F: Sync[F]): F[LocalFile] =
path.iterator().asScala.toList.map(_.toString) match {
case Nil => F.raiseError(new ResolutionFailure("This shouldn't happen - / can't import a dhall expression"))
Expand All @@ -89,15 +89,15 @@ object Canonicalization {
def chain(lhs: LocalFile, rhs: LocalFile): LocalFile = LocalFile(LocalDirs.chain(lhs.dirs, rhs.dirs), rhs.filename)
}

case class LocalDirs(ds: List[String]) {
private case class LocalDirs(ds: List[String]) {
def isRelative = ds.nonEmpty && (ds.head == "." || ds.head == "..")

def canonicalize: LocalDirs = LocalDirs.canonicalize(this)

def chain(other: LocalDirs): LocalDirs = LocalDirs.chain(this, other)
}

object LocalDirs {
private object LocalDirs {
def chain(lhs: LocalDirs, rhs: LocalDirs): LocalDirs = if (rhs.isRelative) LocalDirs(lhs.ds ++ rhs.ds) else rhs

def canonicalize(d: LocalDirs): LocalDirs = d.ds match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.dhallj.imports.ResolveImportsVisitor._
import org.http4s.Headers
import org.http4s.headers.`Access-Control-Allow-Origin`

object CORSComplianceCheck {
object CorsComplianceCheck {

def apply[F[_]](parent: ImportContext, child: ImportContext, headers: Headers)(implicit F: Sync[F]): F[Unit] =
parent match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ import java.nio.file.{Files, Path, Paths}
import cats.effect.Sync
import cats.implicits._

private[dhallj] object Caching {
trait ImportCache[F[_]] {
def get(key: Array[Byte]): F[Option[Array[Byte]]]

trait ImportsCache[F[_]] {
def get(key: Array[Byte]): F[Option[Array[Byte]]]
def put(key: Array[Byte], value: Array[Byte]): F[Unit]
}

def put(key: Array[Byte], value: Array[Byte]): F[Unit]
}
object ImportCache {

/*
* Improves the ergonomics when resolving imports if we don't have to check
* if the cache exists. So if we fail to construct an imports cache,
* we warn and return this instead.
*/
case class NoopImportsCache[F[_]]()(implicit F: Sync[F]) extends ImportsCache[F] {
class NoopImportCache[F[_]](implicit F: Sync[F]) extends ImportCache[F] {
override def get(key: Array[Byte]): F[Option[Array[Byte]]] = F.pure(None)

override def put(key: Array[Byte], value: Array[Byte]): F[Unit] = F.unit
}

case class ImportsCacheImpl[F[_]] private[Caching] (rootDir: Path)(implicit F: Sync[F]) extends ImportsCache[F] {
private class Impl[F[_]](rootDir: Path)(implicit F: Sync[F]) extends ImportCache[F] {
override def get(key: Array[Byte]): F[Option[Array[Byte]]] = {
val p = path(key)
if (Files.exists(p)) {
Expand All @@ -48,20 +48,20 @@ private[dhallj] object Caching {
}
}

def mkImportsCache[F[_] <: AnyRef](rootDir: Path)(implicit F: Sync[F]): F[Option[ImportsCache[F]]] =
def apply[F[_] <: AnyRef](rootDir: Path)(implicit F: Sync[F]): F[Option[ImportCache[F]]] =
for {
_ <- if (!Files.exists(rootDir)) F.delay(Files.createDirectories(rootDir)) else F.unit
perms <- F.delay(Files.isReadable(rootDir) && Files.isWritable(rootDir))
} yield (if (perms) Some(new ImportsCacheImpl[F](rootDir)) else None)
} yield (if (perms) Some(new Impl[F](rootDir)) else None)

def mkImportsCache[F[_] <: AnyRef](cacheName: String)(implicit F: Sync[F]): F[ImportsCache[F]] = {
def makeCacheFromEnvVar(env: String, relativePath: String): F[Option[ImportsCache[F]]] =
def apply[F[_] <: AnyRef](cacheName: String)(implicit F: Sync[F]): F[ImportCache[F]] = {
def makeCacheFromEnvVar(env: String, relativePath: String): F[Option[ImportCache[F]]] =
for {
envValO <- F.delay(sys.env.get(env))
cache <- envValO.fold(F.pure(Option.empty[ImportsCache[F]]))(envVal =>
cache <- envValO.fold(F.pure(Option.empty[ImportCache[F]]))(envVal =>
for {
rootDir <- F.pure(Paths.get(envVal, relativePath, cacheName))
c <- mkImportsCache(rootDir)
c <- apply(rootDir)
} yield c
)
} yield cache
Expand All @@ -71,7 +71,7 @@ private[dhallj] object Caching {
cacheO <- if (isWindows)
makeCacheFromEnvVar("LOCALAPPDATA", "")
else makeCacheFromEnvVar("HOME", ".cache")
cache <- cacheO.fold(warnCacheNotCreated >> F.pure[ImportsCache[F]](NoopImportsCache[F]))(F.pure)
cache <- cacheO.fold[F[ImportCache[F]]](F.as(warnCacheNotCreated, new NoopImportCache[F]))(F.pure)
} yield cache

def isWindows = System.getProperty("os.name").toLowerCase.contains("Windows")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.dhallj.imports

import cats.effect.Sync
import org.dhallj.core.Expr
import org.http4s.client.Client

object ResolveImports {
def apply[F[_] <: AnyRef](expr: Expr)(implicit Client: Client[F], F: Sync[F]): F[Expr] =
F.flatMap(ResolveImportsVisitor[F])(expr.accept(_))

def apply[F[_] <: AnyRef](
semanticCache: ImportCache[F],
semiSemanticCache: ImportCache[F]
)(expr: Expr)(implicit Client: Client[F], F: Sync[F]): F[Expr] =
expr.accept(ResolveImportsVisitor[F](semanticCache, semiSemanticCache))

def apply[F[_] <: AnyRef](
semanticCache: ImportCache[F]
)(expr: Expr)(implicit Client: Client[F], F: Sync[F]): F[Expr] =
expr.accept(ResolveImportsVisitor[F](semanticCache, new ImportCache.NoopImportCache))

final class Ops(val expr: Expr) extends AnyVal {
def resolveImports[F[_] <: AnyRef](implicit Client: Client[F], F: Sync[F]): F[Expr] =
ResolveImports.apply[F](expr)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import org.dhallj.core.Expr.ImportMode
import org.dhallj.core.Expr.Util.typeCheck
import org.dhallj.core._
import org.dhallj.core.binary.Decode
import org.dhallj.imports.Caching.ImportsCache
import org.dhallj.imports.Canonicalization.canonicalize
import org.dhallj.imports.ResolveImportsVisitor._
import org.dhallj.parser.DhallParser
Expand All @@ -24,13 +23,15 @@ import org.http4s.{EntityDecoder, Request}
import scala.collection.mutable.{Map => MMap}

//TODO proper error handling
private[dhallj] case class ResolveImportsVisitor[F[_] <: AnyRef](semanticCache: ImportsCache[F],
semiSemanticCache: ImportsCache[F],
parents: List[ImportContext])(
final private class ResolveImportsVisitor[F[_] <: AnyRef](
semanticCache: ImportCache[F],
semiSemanticCache: ImportCache[F],
parents: List[ImportContext]
)(
implicit Client: Client[F],
F: Sync[F]
) extends LiftVisitor[F](F) {
private var duplicateImportsCache: MMap[ImportContext, Expr] = MMap.empty
private var duplicateImportCache: MMap[ImportContext, Expr] = MMap.empty

override def onOperatorApplication(operator: Operator, lhs: F[Expr], rhs: F[Expr]): F[Expr] =
if (operator == Operator.IMPORT_ALT)
Expand Down Expand Up @@ -131,7 +132,7 @@ private[dhallj] case class ResolveImportsVisitor[F[_] <: AnyRef](semanticCache:
case Successful(resp) =>
for {
s <- EntityDecoder.decodeString(resp)
_ <- if (parents.nonEmpty) CORSComplianceCheck(parents.head, imp, resp.headers) else F.unit
_ <- if (parents.nonEmpty) CorsComplianceCheck(parents.head, imp, resp.headers) else F.unit
} yield s
case _ =>
F.raiseError[String](
Expand Down Expand Up @@ -162,8 +163,8 @@ private[dhallj] case class ResolveImportsVisitor[F[_] <: AnyRef](semanticCache:
text <- fetch(imp)
parsed <- F.delay(DhallParser.parse(text))
resolved <- {
val v = ResolveImportsVisitor[F](semanticCache, semiSemanticCache, imp :: parents)
v.duplicateImportsCache = this.duplicateImportsCache
val v = new ResolveImportsVisitor[F](semanticCache, semiSemanticCache, imp :: parents)
v.duplicateImportCache = this.duplicateImportCache
parsed.accept(v)
}
semiHash = MessageDigest.getInstance("SHA-256").digest(resolved.getEncodedBytes)
Expand All @@ -182,12 +183,12 @@ private[dhallj] case class ResolveImportsVisitor[F[_] <: AnyRef](semanticCache:
}

def resolve(imp: ImportContext, mode: ImportMode, hash: Array[Byte]): F[Expr] =
if (duplicateImportsCache.contains(imp))
F.delay(duplicateImportsCache.get(imp).get)
if (duplicateImportCache.contains(imp))
F.delay(duplicateImportCache.get(imp).get)
else
for {
e <- loadWithSemanticCache(imp, mode, hash)
_ <- F.delay(duplicateImportsCache.put(imp, e))
_ <- F.delay(duplicateImportCache.put(imp, e))
} yield e

def importNonLocation(imp: ImportContext, mode: ImportMode, hash: Array[Byte]) =
Expand All @@ -204,13 +205,15 @@ private[dhallj] case class ResolveImportsVisitor[F[_] <: AnyRef](semanticCache:
}
}

object ResolveImportsVisitor {
private object ResolveImportsVisitor {
def apply[F[_] <: AnyRef: Sync: Client]: F[ResolveImportsVisitor[F]] =
Sync[F].map2(ImportCache[F]("dhall"), ImportCache[F]("dhallj"))(apply[F](_, _))

def mkVisitor[F[_] <: AnyRef: Sync: Client]: F[ResolveImportsVisitor[F]] =
(Caching.mkImportsCache[F]("dhall"), Caching.mkImportsCache[F]("dhallj")).mapN((c, c2) => mkVisitor(c, c2))
def apply[F[_] <: AnyRef: Sync: Client](semanticCache: ImportCache[F],
semiSemanticCache: ImportCache[F]): ResolveImportsVisitor[F] =
new ResolveImportsVisitor(semanticCache, semiSemanticCache, Nil)

def mkVisitor[F[_] <: AnyRef: Sync: Client](semanticCache: ImportsCache[F],
semiSemanticCache: ImportsCache[F]): ResolveImportsVisitor[F] =
ResolveImportsVisitor(semanticCache, semiSemanticCache, Nil)
def apply[F[_] <: AnyRef: Sync: Client](semanticCache: ImportCache[F]): ResolveImportsVisitor[F] =
new ResolveImportsVisitor(semanticCache, new ImportCache.NoopImportCache, Nil)

}
10 changes: 1 addition & 9 deletions modules/imports/src/main/scala/org/dhallj/imports/package.scala
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
package org.dhallj

import _root_.cats.effect.Sync
import _root_.cats.implicits._
import org.dhallj.core.Expr
import org.http4s.client._

package object imports {

implicit class ResolveImports(e: Expr) {
def resolveImports[F[_] <: AnyRef](implicit Client: Client[F], F: Sync[F]): F[Expr] =
ResolveImportsVisitor.mkVisitor >>= (v => e.accept(v))
}

implicit def toResolveImportOps(expr: Expr): ResolveImports.Ops = new ResolveImports.Ops(expr)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.nio.file.Paths

import cats.effect.IO
import munit.FunSuite
import org.dhallj.imports.Canonicalization._
import org.dhallj.imports.Canonicalization.canonicalize
import org.dhallj.imports.ImportContext._
import org.dhallj.parser.DhallParser.parse

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import munit.FunSuite
import org.dhallj.imports.ImportContext._
import org.http4s.{Header, Headers}

class CORSComplianceCheckSuite extends FunSuite {
class CorsComplianceCheckSuite extends FunSuite {

val fooOrigin = new URI("http://foo.org/foo.dhall")
val fooOrigin8080 = new URI("http://foo.org:8080/foo.dhall")
Expand All @@ -18,54 +18,54 @@ class CORSComplianceCheckSuite extends FunSuite {
val localPath = Paths.get("/foo/bar.dhall")

test("Remote - same origin") {
CORSComplianceCheck[IO](Remote(fooOrigin, null), Remote(fooOrigin2, null), Headers.empty).unsafeRunSync()
CorsComplianceCheck[IO](Remote(fooOrigin, null), Remote(fooOrigin2, null), Headers.empty).unsafeRunSync()
}

test("Remote - different origin, allow *") {
CORSComplianceCheck[IO](Remote(fooOrigin, null),
CorsComplianceCheck[IO](Remote(fooOrigin, null),
Remote(barOrigin, null),
Headers.of(Header("Access-Control-Allow-Origin", "*"))).unsafeRunSync()
}

test("Remote - different origin, allow parent authority") {
CORSComplianceCheck[IO](Remote(fooOrigin, null),
CorsComplianceCheck[IO](Remote(fooOrigin, null),
Remote(barOrigin, null),
Headers.of(Header("Access-Control-Allow-Origin", "http://foo.org"))).unsafeRunSync()
}

test("Remote - different origin".fail) {
CORSComplianceCheck[IO](Remote(fooOrigin, null), Remote(barOrigin, null), Headers.empty).unsafeRunSync()
CorsComplianceCheck[IO](Remote(fooOrigin, null), Remote(barOrigin, null), Headers.empty).unsafeRunSync()
}

test("Remote - different origin, cors parent different authority".fail) {
CORSComplianceCheck[IO](Remote(fooOrigin, null),
CorsComplianceCheck[IO](Remote(fooOrigin, null),
Remote(barOrigin, null),
Headers.of(Header("Access-Control-Allow-Origin", "http://bar.org"))).unsafeRunSync()
}

test("Remote - different origin, cors parent different scheme".fail) {
CORSComplianceCheck[IO](Remote(fooOrigin, null),
CorsComplianceCheck[IO](Remote(fooOrigin, null),
Remote(barOrigin, null),
Headers.of(Header("Access-Control-Allow-Origin", "https://foo.org"))).unsafeRunSync()
}

test("Remote - different origin, cors parent different port".fail) {
CORSComplianceCheck[IO](Remote(fooOrigin, null),
CorsComplianceCheck[IO](Remote(fooOrigin, null),
Remote(barOrigin, null),
Headers.of(Header("Access-Control-Allow-Origin", "http://foo.org:8080"))).unsafeRunSync()
}

test("Remote - different origin, cors parent different port 2".fail) {
CORSComplianceCheck[IO](Remote(fooOrigin8080, null),
CorsComplianceCheck[IO](Remote(fooOrigin8080, null),
Remote(barOrigin, null),
Headers.of(Header("Access-Control-Allow-Origin", "http://foo.org"))).unsafeRunSync()
}

test("Local") {
CORSComplianceCheck[IO](Local(localPath), Remote(fooOrigin2, null), Headers.empty).unsafeRunSync()
CorsComplianceCheck[IO](Local(localPath), Remote(fooOrigin2, null), Headers.empty).unsafeRunSync()
}

test("Env") {
CORSComplianceCheck[IO](Env("foo"), Remote(fooOrigin2, null), Headers.empty).unsafeRunSync()
CorsComplianceCheck[IO](Env("foo"), Remote(fooOrigin2, null), Headers.empty).unsafeRunSync()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import java.nio.file.{Files, Path, Paths}
import cats.effect.IO
import cats.implicits._
import munit.FunSuite
import org.dhallj.imports.Caching.{ImportsCache, ImportsCacheImpl}
import scala.reflect.io.Directory

class CachingSuite extends FunSuite {

val rootDir = new FunFixture[(ImportsCache[IO], Path)](
val rootDir = new FunFixture[(ImportCache[IO], Path)](
setup = { test =>
val rootDir = Files.createTempDirectory(test.name).resolve("dhall")
Caching.mkImportsCache[IO](rootDir).unsafeRunSync.get -> rootDir
ImportCache[IO](rootDir).unsafeRunSync.get -> rootDir
},
teardown = {
case (_, rootDir) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import cats.implicits._
import munit.FunSuite
import org.dhallj.core.Expr
import org.dhallj.core.binary.Decode
import org.dhallj.imports.Caching.{ImportsCache, NoopImportsCache}
import org.dhallj.parser.DhallParser.parse
import org.http4s.client._
import org.http4s.client.blaze._
Expand Down Expand Up @@ -222,14 +221,14 @@ class ImportResolutionSuite extends FunSuite {
e.resolveImports[IO].map(_.normalize)
}.unsafeRunSync

private def resolveWithCustomCache(cache: ImportsCache[IO], e: Expr): IO[Expr] =
private def resolveWithCustomCache(cache: ImportCache[IO], e: Expr): IO[Expr] =
client.use { c =>
implicit val http: Client[IO] = c

e.accept(ResolveImportsVisitor.mkVisitor(cache, NoopImportsCache[IO]))
e.accept(ResolveImportsVisitor[IO](cache))
}

private case class InMemoryCache() extends ImportsCache[IO] {
private case class InMemoryCache() extends ImportCache[IO] {

private val store: Ref[IO, Map[List[Byte], Array[Byte]]] = Ref.unsafe(Map.empty)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import org.dhallj.core.binary.Decode.decode
import org.dhallj.imports.mini.Resolver
import org.dhallj.parser.DhallParser

import org.dhallj.imports._
import org.dhallj.imports.Caching._
import org.dhallj.imports.{ImportCache, ResolveImports}
import org.http4s.client._
import org.http4s.client.blaze._

Expand Down Expand Up @@ -60,7 +59,7 @@ trait CachedResolvingInput extends Input[Expr] {
implicit val cs: ContextShift[IO] = IO.contextShift(global)
BlazeClientBuilder[IO](global).resource.use { client =>
implicit val c: Client[IO] = client
parsed.accept(ResolveImportsVisitor.mkVisitor[IO].unsafeRunSync)
ResolveImports(parsed)
}.unsafeRunSync
}
}
Expand All @@ -77,7 +76,7 @@ trait ResolvingInput extends Input[Expr] {
implicit val cs: ContextShift[IO] = IO.contextShift(global)
BlazeClientBuilder[IO](global).resource.use { client =>
implicit val c: Client[IO] = client
parsed.accept(ResolveImportsVisitor.mkVisitor(NoopImportsCache[IO], NoopImportsCache[IO]))
ResolveImports(new ImportCache.NoopImportCache[IO], new ImportCache.NoopImportCache[IO])(parsed)
}.unsafeRunSync
}
}
Expand Down
Loading

0 comments on commit cce9bd7

Please sign in to comment.