diff --git a/modules/imports/src/main/scala/org/dhallj/imports/ResolveImportsVisitor.scala b/modules/imports/src/main/scala/org/dhallj/imports/ResolveImportsVisitor.scala index 37d0fcfd..c6dc9246 100644 --- a/modules/imports/src/main/scala/org/dhallj/imports/ResolveImportsVisitor.scala +++ b/modules/imports/src/main/scala/org/dhallj/imports/ResolveImportsVisitor.scala @@ -138,7 +138,7 @@ final private class ResolveImportsVisitor[F[_] <: AnyRef]( } yield v case ImportContext.Remote(uri, using) => for { - headers <- F.pure(ToHeaders(`using`)) + headers <- F.fromOption(ToHeaders(`using`), new ResolutionFailure("Invalid using clause")) req <- F.pure(Request[F](uri = unsafeFromString(uri.toString), headers = headers)) resp <- Client.fetch[String](req) { case Successful(resp) => diff --git a/modules/imports/src/main/scala/org/dhallj/imports/ToHeaders.scala b/modules/imports/src/main/scala/org/dhallj/imports/ToHeaders.scala index 4f2834a6..96e635f4 100644 --- a/modules/imports/src/main/scala/org/dhallj/imports/ToHeaders.scala +++ b/modules/imports/src/main/scala/org/dhallj/imports/ToHeaders.scala @@ -1,54 +1,89 @@ package org.dhallj.imports +import java.util.AbstractMap.SimpleImmutableEntry +import java.util.Map.Entry import org.dhallj.core.Expr import org.dhallj.core.Expr.Util.{asListLiteral, asRecordLiteral, asSimpleTextLiteral} +import org.dhallj.core.typechecking.TypeCheckFailure import org.http4s.{Header, Headers} import org.typelevel.ci.CIString import scala.collection.JavaConverters._ object ToHeaders { + private val validType1: Expr = Expr.makeApplication( + Expr.Constants.LIST, + Expr.makeRecordType( + Array[Entry[String, Expr]]( + new SimpleImmutableEntry("mapKey", Expr.Constants.TEXT), + new SimpleImmutableEntry("mapValue", Expr.Constants.TEXT) + ) + ) + ) + + private val validType2: Expr = Expr.makeApplication( + Expr.Constants.LIST, + Expr.makeRecordType( + Array[Entry[String, Expr]]( + new SimpleImmutableEntry("header", Expr.Constants.TEXT), + new SimpleImmutableEntry("value", Expr.Constants.TEXT) + ) + ) + ) + + private def isValidType(expr: Expr): Boolean = { + try { + val tpe = Expr.Util.typeCheck(expr) + + tpe == validType1 || tpe == validType2 + } catch { + case _: TypeCheckFailure => false + } + } // See https://discourse.dhall-lang.org/t/valid-expressions-for-using-headers/205 // For the moment, this is consistent with the Haskell implementation - def apply(expr: Expr): Headers = - if (expr eq null) Headers.empty + def apply(expr: Expr): Option[Headers] = + if (expr eq null) Some(Headers.empty) else { - //TODO do we need to .accept(this) on expr? - val e = expr.normalize - val l = asListLiteral(e) - if (l eq null) { - Headers.empty + if (!isValidType(expr)) { + None } else { - val hs: List[Header.Raw] = l.asScala.toList.flatMap { e => - // e should have type `List { header : Text, value Text }` - // or `List { mapKey : Text, mapValue Text }` - val r = asRecordLiteral(e) - if (r eq null) { - None - } else { - if (r.size == 2) { - val map = r.asScala.map(e => e.getKey -> e.getValue).toMap - if (map.contains("header") && map.contains("value")) { - val key = asSimpleTextLiteral(map("header")) - val value = asSimpleTextLiteral(map("value")) - - if ((key ne null) && (value ne null)) { - Some(Header.Raw(CIString(key), value)) - } else None - } else if (map.contains("mapKey") && map.contains("mapValue")) { - val key = asSimpleTextLiteral(map("mapKey")) - val value = asSimpleTextLiteral(map("mapValue")) + //TODO do we need to .accept(this) on expr? + val e = expr.normalize + val l = asListLiteral(e) + if (l eq null) { + Some(Headers.empty) + } else { + val hs: List[Header.Raw] = l.asScala.toList.flatMap { e => + // e should have type `List { header : Text, value Text }` + // or `List { mapKey : Text, mapValue Text }` + val r = asRecordLiteral(e) + if (r eq null) { + None + } else { + if (r.size == 2) { + val map = r.asScala.map(e => e.getKey -> e.getValue).toMap + if (map.contains("header") && map.contains("value")) { + val key = asSimpleTextLiteral(map("header")) + val value = asSimpleTextLiteral(map("value")) + + if ((key ne null) && (value ne null)) { + Some(Header.Raw(CIString(key), value)) + } else None + } else if (map.contains("mapKey") && map.contains("mapValue")) { + val key = asSimpleTextLiteral(map("mapKey")) + val value = asSimpleTextLiteral(map("mapValue")) - if ((key ne null) && (value ne null)) { - Some(Header.Raw(CIString(key), value)) + if ((key ne null) && (value ne null)) { + Some(Header.Raw(CIString(key), value)) + } else None } else None } else None - } else None + } } + Some(Headers(hs)) } - Headers(hs) } } - } diff --git a/modules/imports/src/test/scala/org/dhallj/imports/ToHeadersSuite.scala b/modules/imports/src/test/scala/org/dhallj/imports/ToHeadersSuite.scala index 1c3c46c1..f4245029 100644 --- a/modules/imports/src/test/scala/org/dhallj/imports/ToHeadersSuite.scala +++ b/modules/imports/src/test/scala/org/dhallj/imports/ToHeadersSuite.scala @@ -28,7 +28,7 @@ class ToHeadersSuite extends FunSuite { ) ) - assertEquals(ToHeaders(expr), expected) + assertEquals(ToHeaders(expr), Some(expected)) } test("Success case 2") { @@ -50,7 +50,7 @@ class ToHeadersSuite extends FunSuite { ) ) - assertEquals(ToHeaders(expr), expected) + assertEquals(ToHeaders(expr), Some(expected)) } test("Failure case 1") { @@ -65,17 +65,13 @@ class ToHeadersSuite extends FunSuite { ) ) - val expected = Headers(Nil) - - assertEquals(ToHeaders(expr), expected) + assertEquals(ToHeaders(expr), None) } test("Failure case 2") { val expr = Expr.makeTextLiteral("foo") - val expected = Headers(Nil) - - assertEquals(ToHeaders(expr), expected) + assertEquals(ToHeaders(expr), None) } }