scalacompilationdefensive-programming

Expose private case class in Scala


I have a situation where a codepath is given some Path's which it uses to query a database. It so happens that sometimes some of these paths need to go through a translation service, to map them to updated Paths before the query. Essentially the simplified flow is

val paths: List[Path] = getPaths()

val pathTranslationOpt: Option[Map[Path, Path]] = getTranslationOpt()

paths.foreach { p =>
  val pathToUse = 
    pathTranslationOpt
      .flatMap(_.get(p))
      .getOrElse(p)

  someFunctionsUsingThePath(pathToUse)
}

I'm worried about a situation in which someone accidentally calls someFunctionsUsingThePath with p, forgetting to perform the translation. This could be caught at compile time modifying the code slightly

val paths: List[Path] = getPaths()
 // let NewPath be a case class defined in the scope of the translate function as 
// case class NewPath(path: Path)
val pathTranslationOpt: Option[Map[Path, NewPath]] = getTranslationOpt()

paths.foreach { p =>
  // Have the function explicitely take a NewPath so the user cannot forget to call the translation service
  someFunctionsUsingThePath(
    newPathOpt = 
      pathTranslationOpt.flatMap(_.get(p)),
    fallbackPath = p
  )
}

This seems to be safer to me, as forgetting to call the translation service results in a compile time error. However, a negligent user could simply pass in Some(NewPath(p)) to the function call, defeating the purpose.

Is there a way to make NewPath such that it can be used freely, but only constructed from the translation call?


Solution

  • Try this:

    case class NewPath private(path: Path)
    
    object NewPath {
      def apply(path: Path): NewPath =
        NewPath(
          pathTranslationOpt.flatMap(_.get(path)).getOrElse(path)
        )
    }
    
    def someFunctionsUsingThePath(path: NewPath) = ???
    
    paths.foreach { p =>
      someFunctionsUsingThePath(NewPath(p))
    }
    

    NewPath can only be created via the apply method in the companion object, which translates the path if necessary but otherwise leaves it unchanged.