Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does not work if doctype is returned instead of Frag #44

Closed
mabasic opened this issue Sep 16, 2022 · 4 comments · Fixed by #53
Closed

Does not work if doctype is returned instead of Frag #44

mabasic opened this issue Sep 16, 2022 · 4 comments · Fixed by #53

Comments

@mabasic
Copy link
Contributor

mabasic commented Sep 16, 2022

https://github.com/http4s/http4s-scalatags/blob/series/0.24/scalatags/src/main/scala/org/http4s/scalatags/ScalatagsInstances.scala#L25

https://github.com/com-lihaoyi/scalatags/blob/0cfff0c972d7e198210379eeac1a18ef6cd2d6bf/scalatags/src/scalatags/Text.scala#L70

Does not work because the return type of doctype is doctype:

case GET -> Root =>
      Ok(
        doctype("html")(html(
          lang := "hr",
          head(
             title("Example"),
          ),
         body(
           "Test"
          )
        )
      )
    )

Works because the return type of html is Frag[Builder, String] or something like that.

case GET -> Root =>
      Ok(
        html(
          lang := "hr",
          head(
             title("Example"),
          ),
         body(
           "Test"
          )
        )
      )

I can't decide where to create a fix in scalatags and have doctype return Frag, or in http4s and have it correctly handle doctype and Frag.

@armanbilge We talked about this on discord a few days ago.

@armanbilge
Copy link
Member

Thanks for reporting, much appreciated! Unfortunately this project is only lightly-maintained at the moment (see #1, feel free to volunteer!) but I hope if you have a breakthrough you will report back and/or open a PR.

@mabasic
Copy link
Contributor Author

mabasic commented Sep 27, 2022

@armanbilge Can I update this project to use scala 3? I am really having difficulties with fixing this.

My project is using scala 3:

trait ScalatagsInstances {
  implicit def scalatagsEncoder[C <: Frag[_, String] | doctype](implicit
      charset: Charset = `UTF-8`
  ): EntityEncoder.Pure[C] =
    contentEncoder(MediaType.text.html)

  private def contentEncoder[C <: Frag[_, String] | doctype](
      mediaType: MediaType
  )(implicit charset: Charset): EntityEncoder.Pure[C] =
    EntityEncoder.stringEncoder
//      .contramap[C](content => content.render)
      .contramap[C](_ match
        case content: doctype         => content.render
        case content: Frag[_, String] => content.render
      )
      .withContentType(`Content-Type`(mediaType, charset))
}

This throws this warning, but it compiles and runs:

32 |        case content: Frag[_, String] => content.render
   |             ^
   |the type test for scalatags.generic.Frag[_, String] @_$3 cannot be checked at runtime

The people at the discord server told me to look into:

BalmungSan — Today at 12:10 AM
Type erasure is the term you want to googler for
Basically, there is no safe way you can check at runtime that your Frag will contain Strings
Needing to check types at runtime is usually a sign of bad design.

So I did a bit of researching and found this: https://stackoverflow.com/a/38572269

In Scala, generics are erased at runtime, which means that the runtime type of List[Int] and List[Boolean] is actually the same. > This is because the JVM as a whole erases generic types. All this is due because the JVM wanted to remain backwards compatible way back when generics were first introduced...

Maybe this would be worth reading also: https://squidarth.com/scala/types/2019/01/11/type-erasure-scala.html

I don't know how to write this in scala 2 since there are no union types:

trait ScalatagsInstances {
  implicit def scalatagsEncoder[F[_], C <: Frag[_, String]](implicit
      charset: Charset = `UTF-8`
  ): EntityEncoder[F, C] =
    contentEncoder(MediaType.text.html)

  private def contentEncoder[F[_], C <: Frag[_, String]](
      mediaType: MediaType
  )(implicit charset: Charset): EntityEncoder[F, C] =
    EntityEncoder.stringEncoder[F]
      //      .contramap[C](content => content.render)
      .contramap[C] {
        case content: doctype => content.render
        case content: Frag[_, String] => content.render
      }
      .withContentType(`Content-Type`(mediaType, charset))
}

I have found this to get around the union type, but I can't figure it out: https://www.baeldung.com/scala/type-disjunction

Do you have any idea on how to solve this?

@armanbilge
Copy link
Member

@mabasic have you consider defining a new EntityEncoder specifically for doctype? Instead of trying to reuse the existing one with a union.

@mabasic
Copy link
Contributor Author

mabasic commented Sep 27, 2022

@mabasic have you consider defining a new EntityEncoder specifically for doctype? Instead of trying to reuse the existing one with a union.

I have not xD I'll do that tomorrow... Thank you for the suggestion ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants