diff --git a/url-dsl/src/main/scala/urldsl/language/AllImpl.scala b/url-dsl/src/main/scala/urldsl/language/AllImpl.scala index 2cbf110..5913e32 100644 --- a/url-dsl/src/main/scala/urldsl/language/AllImpl.scala +++ b/url-dsl/src/main/scala/urldsl/language/AllImpl.scala @@ -3,9 +3,9 @@ package urldsl.language import urldsl.errors.{FragmentMatchingError, ParamMatchingError, PathMatchingError} final class AllImpl[P, Q, F] private (implicit - protected val pathError: PathMatchingError[P], - protected val queryError: ParamMatchingError[Q], - protected val fragmentError: FragmentMatchingError[F] + val pathError: PathMatchingError[P], + val queryError: ParamMatchingError[Q], + val fragmentError: FragmentMatchingError[F] ) extends PathSegmentImpl[P] with QueryParametersImpl[Q] with FragmentImpl[F] diff --git a/url-dsl/src/main/scala/urldsl/language/Fragment.scala b/url-dsl/src/main/scala/urldsl/language/Fragment.scala index d5dcea5..ee6fe70 100644 --- a/url-dsl/src/main/scala/urldsl/language/Fragment.scala +++ b/url-dsl/src/main/scala/urldsl/language/Fragment.scala @@ -14,7 +14,7 @@ import scala.reflect.ClassTag * @tparam E * type of the error that this PathSegment produces on "illegal" url paths. */ -trait Fragment[T, +E] extends UrlPart[T, E] { +trait Fragment[T, E] extends UrlPart[T, E] { import Fragment.factory diff --git a/url-dsl/src/main/scala/urldsl/language/PathQueryFragmentRepr.scala b/url-dsl/src/main/scala/urldsl/language/PathQueryFragmentRepr.scala index 771aab0..debabb8 100644 --- a/url-dsl/src/main/scala/urldsl/language/PathQueryFragmentRepr.scala +++ b/url-dsl/src/main/scala/urldsl/language/PathQueryFragmentRepr.scala @@ -13,11 +13,11 @@ import urldsl.vocabulary.{ final class PathQueryFragmentRepr[ PathType, - +PathError, + PathError, ParamsType, - +ParamsError, + ParamsError, FragmentType, - +FragmentError + FragmentError ] private[language] ( pathSegment: PathSegment[PathType, PathError], queryParams: QueryParameters[ParamsType, ParamsError], diff --git a/url-dsl/src/main/scala/urldsl/language/PathSegment.scala b/url-dsl/src/main/scala/urldsl/language/PathSegment.scala index ffad3d1..46bb543 100644 --- a/url-dsl/src/main/scala/urldsl/language/PathSegment.scala +++ b/url-dsl/src/main/scala/urldsl/language/PathSegment.scala @@ -14,7 +14,7 @@ import scala.language.implicitConversions * @tparam A * type of the error that this PathSegment produces on "illegal" url paths. */ -trait PathSegment[T, +A] extends UrlPart[T, A] { +trait PathSegment[T, A] extends UrlPart[T, A] { /** Tries to match the list of [[urldsl.vocabulary.Segment]]s to create an instance of `T`. If it can not, it returns * an error indicating the reason of the failure. If it could, it returns the value of `T`, as well as the list of @@ -31,6 +31,30 @@ trait PathSegment[T, +A] extends UrlPart[T, A] { */ def matchSegments(segments: List[Segment]): Either[A, PathMatchOutput[T]] + protected implicit def errorImpl: PathMatchingError[A] + + /** Tries to match the provided list of [[urldsl.vocabulary.Segment]]s to create an instance of `T`. + * + * If it can't, it returns an error indicating the reason of the failure. If it can but there are unused segments + * left over, it fails with a `endOfSegmentRequired` error. If it can, it returns the output. + * + * It is thus similar to [[matchSegments]], but requiring that all segments have been consumed. + * + * @see + * [[matchSegments]] + * + * @param segments + * The list of [[urldsl.vocabulary.Segment]] to match this path segment again. + * @return + */ + def matchFullSegments(segments: List[Segment]): Either[A, T] = for { + matchOuptput <- matchSegments(segments) + t <- matchOuptput.unusedSegments match { + case Nil => Right(matchOuptput.output) + case segments => Left(errorImpl.endOfSegmentRequired(segments)) + } + } yield t + /** Matches the given raw `url` using the given [[urldsl.url.UrlStringParserGenerator]] for creating a * [[urldsl.url.UrlStringParser]]. * @@ -52,10 +76,10 @@ trait PathSegment[T, +A] extends UrlPart[T, A] { url: String, urlStringParserGenerator: UrlStringParserGenerator = UrlStringParserGenerator.defaultUrlStringParserGenerator ): Either[A, T] = - matchSegments(urlStringParserGenerator.parser(url).segments).map(_.output) + matchFullSegments(urlStringParserGenerator.parser(url).segments) def matchPath(path: String, decoder: UrlStringDecoder = UrlStringDecoder.defaultDecoder): Either[A, T] = - matchSegments(decoder.decodePath(path)).map(_.output) + matchFullSegments(decoder.decodePath(path)) /** Generate a list of segments representing the argument `t`. * @@ -85,8 +109,8 @@ trait PathSegment[T, +A] extends UrlPart[T, A] { /** Concatenates `this` [[urldsl.language.PathSegment]] with `that` one, "tupling" the types with the [[Composition]] * rules. */ - final def /[U, A1 >: A](that: PathSegment[U, A1])(implicit c: Composition[T, U]): PathSegment[c.Composed, A1] = - PathSegment.factory[c.Composed, A1]( + final def /[U](that: PathSegment[U, A])(implicit c: Composition[T, U]): PathSegment[c.Composed, A] = + PathSegment.factory[c.Composed, A]( (segments: List[Segment]) => for { firstOut <- this.matchSegments(segments) @@ -118,8 +142,8 @@ trait PathSegment[T, +A] extends UrlPart[T, A] { * - in a multi-part segment, ensure consistency between the different component (e.g., a range of two integers * that should not be too large...) */ - final def filter[A1 >: A](predicate: T => Boolean, error: List[Segment] => A1): PathSegment[T, A1] = - PathSegment.factory[T, A1]( + final def filter(predicate: T => Boolean, error: List[Segment] => A): PathSegment[T, A] = + PathSegment.factory[T, A]( (segments: List[Segment]) => matchSegments(segments) .filterOrElse(((_: PathMatchOutput[T]).output).andThen(predicate), error(segments)), @@ -127,7 +151,7 @@ trait PathSegment[T, +A] extends UrlPart[T, A] { ) /** Sugar for when `A =:= DummyError` */ - final def filter(predicate: T => Boolean)(implicit ev: A <:< DummyError): PathSegment[T, DummyError] = { + final def filter(predicate: T => Boolean)(implicit ev: A =:= DummyError): PathSegment[T, DummyError] = { // type F[+E] = PathSegment[T, E] // ev.liftCo[F].apply(this).filter(predicate, _ => DummyError.dummyError) // we keep the ugliness below while supporting 2.12 todo[scala3] remove this @@ -137,8 +161,8 @@ trait PathSegment[T, +A] extends UrlPart[T, A] { /** Builds a [[PathSegment]] that first tries to match with this one, then tries to match with `that` one. If both * fail, the error of the second is returned (todo[behaviour]: should that change?) */ - final def ||[U, A1 >: A](that: PathSegment[U, A1]): PathSegment[Either[T, U], A1] = - PathSegment.factory[Either[T, U], A1]( + final def ||[U](that: PathSegment[U, A]): PathSegment[Either[T, U], A] = + PathSegment.factory[Either[T, U], A]( segments => this.matchSegments(segments) match { case Right(output) => Right(PathMatchOutput(Left(output.output), output.unusedSegments)) @@ -169,19 +193,21 @@ trait PathSegment[T, +A] extends UrlPart[T, A] { (_: Unit) => createSegments(default) ) + final def ignoreRemaining: PathSegment[T, A] = this / PathSegment.remainingSegments.ignore(Nil) + /** Forgets the information contained in the path parameter by injecting one. This turn this "dynamic" [[PathSegment]] * into a fix one. */ - final def provide[A1 >: A]( + final def provide( t: T - )(implicit pathMatchingError: PathMatchingError[A1], printer: Printer[T]): PathSegment[Unit, A1] = - PathSegment.factory[Unit, A1]( + )(implicit printer: Printer[T]): PathSegment[Unit, A] = + PathSegment.factory[Unit, A]( segments => for { tMatch <- matchSegments(segments) PathMatchOutput(tOutput, unusedSegments) = tMatch unitMatched <- - if (tOutput != t) Left(pathMatchingError.wrongValue(printer(t), printer(tOutput))) + if (tOutput != t) Left(errorImpl.wrongValue(printer(t), printer(tOutput))) else Right(PathMatchOutput((), unusedSegments)) } yield unitMatched, (_: Unit) => createSegments(t) @@ -211,16 +237,18 @@ object PathSegment { def factory[T, A]( matching: List[Segment] => Either[A, PathMatchOutput[T]], creating: T => List[Segment] - ): PathSegment[T, A] = new PathSegment[T, A] { + )(implicit errors: PathMatchingError[A]): PathSegment[T, A] = new PathSegment[T, A] { + protected def errorImpl: PathMatchingError[A] = errors + def matchSegments(segments: List[Segment]): Either[A, PathMatchOutput[T]] = matching(segments) def createSegments(t: T): List[Segment] = creating(t) } /** Simple path segment that matches everything by passing segments down the line. */ - final def empty: PathSegment[Unit, Nothing] = - factory[Unit, Nothing](segments => Right(PathMatchOutput((), segments)), _ => Nil) - final def root: PathSegment[Unit, Nothing] = empty + final def empty[A](implicit pathMatchingError: PathMatchingError[A]): PathSegment[Unit, A] = + factory[Unit, A](segments => Right(PathMatchOutput((), segments)), _ => Nil) + final def root[A](implicit pathMatchingError: PathMatchingError[A]): PathSegment[Unit, A] = empty /** Simple path segment that matches nothing. This is the neutral of the || operator. */ final def noMatch[A](implicit pathMatchingError: PathMatchingError[A]): PathSegment[Unit, A] = @@ -274,10 +302,11 @@ object PathSegment { * * This can be useful for static resources. */ - final def remainingSegments[A]: PathSegment[List[String], A] = factory[List[String], A]( - segments => Right(PathMatchOutput(segments.map(_.content), Nil)), - _.map(Segment.apply) - ) + final def remainingSegments[A](implicit pathMatchingError: PathMatchingError[A]): PathSegment[List[String], A] = + factory[List[String], A]( + segments => Right(PathMatchOutput(segments.map(_.content), Nil)), + _.map(Segment.apply) + ) /** [[PathSegment]] that matches one of the given different possibilities. * diff --git a/url-dsl/src/main/scala/urldsl/language/PathSegmentImpl.scala b/url-dsl/src/main/scala/urldsl/language/PathSegmentImpl.scala index 8a0054d..4bd1799 100644 --- a/url-dsl/src/main/scala/urldsl/language/PathSegmentImpl.scala +++ b/url-dsl/src/main/scala/urldsl/language/PathSegmentImpl.scala @@ -28,8 +28,9 @@ trait PathSegmentImpl[A] { /** implementation of [[urldsl.errors.PathMatchingError]] for type A. */ implicit protected val pathError: PathMatchingError[A] - val root: PathSegment[Unit, A] = PathSegment.root - val remainingSegments: PathSegment[List[String], A] = PathSegment.remainingSegments + lazy val root: PathSegment[Unit, A] = PathSegment.root + lazy val remainingSegments: PathSegment[List[String], A] = PathSegment.remainingSegments + lazy val ignoreRemaining: PathSegment[Unit, A] = remainingSegments.ignore(Nil) lazy val endOfSegments: PathSegment[Unit, A] = PathSegment.endOfSegments lazy val noMatch: PathSegment[Unit, A] = PathSegment.noMatch[A] @@ -54,13 +55,15 @@ trait PathSegmentImpl[A] { (_: Unit) => Segment(printer(t)) ) + type Path[T] = PathSegment[T, A] + } object PathSegmentImpl { /** Invoker. */ def apply[A](implicit error: PathMatchingError[A]): PathSegmentImpl[A] = new PathSegmentImpl[A] { - implicit protected val pathError: PathMatchingError[A] = error + implicit val pathError: PathMatchingError[A] = error } } diff --git a/url-dsl/src/main/scala/urldsl/language/PathSegmentWithQueryParams.scala b/url-dsl/src/main/scala/urldsl/language/PathSegmentWithQueryParams.scala index 771aecd..b5dc0f6 100644 --- a/url-dsl/src/main/scala/urldsl/language/PathSegmentWithQueryParams.scala +++ b/url-dsl/src/main/scala/urldsl/language/PathSegmentWithQueryParams.scala @@ -4,7 +4,7 @@ import app.tulz.tuplez.Composition import urldsl.url.{UrlStringDecoder, UrlStringGenerator, UrlStringParserGenerator} import urldsl.vocabulary._ -final class PathSegmentWithQueryParams[PathType, +PathError, ParamsType, +ParamsError] private[language] ( +final class PathSegmentWithQueryParams[PathType, PathError, ParamsType, ParamsError] private[language] ( pathSegment: PathSegment[PathType, PathError], queryParams: QueryParameters[ParamsType, ParamsError] ) extends UrlPart[UrlMatching[PathType, ParamsType], Either[PathError, ParamsError]] { @@ -73,13 +73,13 @@ final class PathSegmentWithQueryParams[PathType, +PathError, ParamsType, +Params ): String = pathSegment.createPath(path, generator) ++ "?" ++ queryParams.createParamsString(params, generator) - def &[OtherParamsType, ParamsError1 >: ParamsError](otherParams: QueryParameters[OtherParamsType, ParamsError1])( - implicit c: Composition[ParamsType, OtherParamsType] - ): PathSegmentWithQueryParams[PathType, PathError, c.Composed, ParamsError1] = - new PathSegmentWithQueryParams[PathType, PathError, c.Composed, ParamsError1]( + def &[OtherParamsType](otherParams: QueryParameters[OtherParamsType, ParamsError])(implicit + c: Composition[ParamsType, OtherParamsType] + ): PathSegmentWithQueryParams[PathType, PathError, c.Composed, ParamsError] = + new PathSegmentWithQueryParams[PathType, PathError, c.Composed, ParamsError]( pathSegment, (queryParams & otherParams) - .asInstanceOf[QueryParameters[c.Composed, ParamsError1]] // not necessary but IntelliJ complains. + .asInstanceOf[QueryParameters[c.Composed, ParamsError]] // not necessary but IntelliJ complains. ) def withFragment[FragmentType, FragmentError]( diff --git a/url-dsl/src/main/scala/urldsl/language/QueryParameters.scala b/url-dsl/src/main/scala/urldsl/language/QueryParameters.scala index d8fe0e9..156103e 100644 --- a/url-dsl/src/main/scala/urldsl/language/QueryParameters.scala +++ b/url-dsl/src/main/scala/urldsl/language/QueryParameters.scala @@ -5,7 +5,7 @@ import urldsl.errors.{DummyError, ParamMatchingError, SimpleParamMatchingError} import urldsl.url.{UrlStringDecoder, UrlStringGenerator, UrlStringParserGenerator} import urldsl.vocabulary._ -trait QueryParameters[Q, +A] extends UrlPart[Q, A] { +trait QueryParameters[Q, A] extends UrlPart[Q, A] { import QueryParameters._ @@ -63,10 +63,10 @@ trait QueryParameters[Q, +A] extends UrlPart[Q, A] { * called, you can end up with "Q = (Int, String)" or "Q = (String, Int)". This property is called * "QuasiCommutativity" in the tests. */ - final def &[R, A1 >: A](that: QueryParameters[R, A1])(implicit + final def &[R](that: QueryParameters[R, A])(implicit c: Composition[Q, R] - ): QueryParameters[c.Composed, A1] = - factory[c.Composed, A1]( + ): QueryParameters[c.Composed, A] = + factory[c.Composed, A]( (params: Map[String, Param]) => for { firstMatch <- this.matchParams(params) @@ -109,7 +109,7 @@ trait QueryParameters[Q, +A] extends UrlPart[Q, A] { * @return * a new [[QueryParameters]] instance with the same types */ - final def filter[A1 >: A](predicate: Q => Boolean, error: Map[String, Param] => A1): QueryParameters[Q, A1] = factory( + final def filter(predicate: Q => Boolean, error: Map[String, Param] => A): QueryParameters[Q, A] = factory( (params: Map[String, Param]) => matchParams(params).filterOrElse(((_: ParamMatchOutput[Q]).output).andThen(predicate), error(params)), createParams @@ -131,16 +131,6 @@ trait QueryParameters[Q, +A] extends UrlPart[Q, A] { (codec.rightToLeft _).andThen(createParams) ) - /** Associates this [[QueryParameters]] with the given [[Fragment]] in order to match raw urls satisfying both - * conditions, and returning the outputs from both. - * - * The path part of the url will be *ignored* (and will return Unit). - */ - final def withFragment[FragmentType, FragmentError]( - fragment: Fragment[FragmentType, FragmentError] - ): PathQueryFragmentRepr[Unit, Nothing, Q, A, FragmentType, FragmentError] = - new PathQueryFragmentRepr(PathSegment.root, this, fragment) - } object QueryParameters { @@ -153,13 +143,13 @@ object QueryParameters { def createParams(q: Q): Map[String, Param] = creating(q) } - final def empty: QueryParameters[Unit, Nothing] = factory[Unit, Nothing]( + final def empty[A]: QueryParameters[Unit, A] = factory[Unit, A]( (params: Map[String, Param]) => Right(ParamMatchOutput((), params)), _ => Map() ) /** Alias for empty which seems to better reflect the semantic. */ - final def ignore: QueryParameters[Unit, Nothing] = empty + final def ignore[A]: QueryParameters[Unit, A] = empty final def simpleQueryParam[Q, A]( paramName: String, diff --git a/url-dsl/src/main/scala/urldsl/language/UrlPart.scala b/url-dsl/src/main/scala/urldsl/language/UrlPart.scala index e2419e2..e96687f 100644 --- a/url-dsl/src/main/scala/urldsl/language/UrlPart.scala +++ b/url-dsl/src/main/scala/urldsl/language/UrlPart.scala @@ -9,7 +9,7 @@ import urldsl.url.{UrlStringGenerator, UrlStringParserGenerator} * A [[UrlPart]] is also able to generate its corresponding part of the URL by ingesting an element of type T. When * doing that, it outputs a String (whose semantic may vary depending on the type of [[UrlPart]] you are dealing with). */ -trait UrlPart[T, +E] { +trait UrlPart[T, E] { def matchRawUrl( url: String, @@ -40,10 +40,4 @@ object UrlPart { def createPart(t: T, encoder: UrlStringGenerator): String = generator(t, encoder) } - /** Type alias when you don't care about what kind of error is issued. [[Any]] can seem weird, but it has to be - * understood as "since it can fail with anything, I won't be able to do anything with the error, which means that I - * can only check whether it failed or not". - */ - type SimpleUrlPart[T] = UrlPart[T, Any] - } diff --git a/url-dsl/src/test/scala/urldsl/examples/CombinedExamples.scala b/url-dsl/src/test/scala/urldsl/examples/CombinedExamples.scala index 80194b8..ab0ded9 100644 --- a/url-dsl/src/test/scala/urldsl/examples/CombinedExamples.scala +++ b/url-dsl/src/test/scala/urldsl/examples/CombinedExamples.scala @@ -37,7 +37,7 @@ final class CombinedExamples extends AnyFlatSpec with Matchers { ) /** And we can of course combine a [[urldsl.language.QueryParameters]] and a [[urldsl.language.Fragment]] */ - queryPart.withFragment(fragmentPart).matchRawUrl(sampleUrl) should be( + (root ? queryPart).withFragment(fragmentPart).matchRawUrl(sampleUrl) should be( Right(PathQueryFragmentMatching((), ("stuff", List(2, 3)), "the-ref")) ) @@ -56,7 +56,7 @@ final class CombinedExamples extends AnyFlatSpec with Matchers { """foo/23/true#some-other-ref""" ) - queryPart + (root ? queryPart) .withFragment(fragmentPart) .createPart(PathQueryFragmentMatching((), ("stuff", List(2, 3)), "the-ref")) should be( """?bar=stuff&other=2&other=3#the-ref""" diff --git a/url-dsl/src/test/scala/urldsl/examples/PathSegmentExamples.scala b/url-dsl/src/test/scala/urldsl/examples/PathSegmentExamples.scala index a4dd9bc..471464d 100644 --- a/url-dsl/src/test/scala/urldsl/examples/PathSegmentExamples.scala +++ b/url-dsl/src/test/scala/urldsl/examples/PathSegmentExamples.scala @@ -4,6 +4,7 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import urldsl.errors.SimplePathMatchingError import urldsl.vocabulary.{Codec, Segment} +import urldsl.url.UrlStringParserGenerator /** This class exposes some example of usage of the [[urldsl.language.PathSegment]] class. * @@ -12,31 +13,51 @@ import urldsl.vocabulary.{Codec, Segment} final class PathSegmentExamples extends AnyFlatSpec with Matchers { /** The following import brings everything you need for [[urldsl.language.PathSegment]] usage. */ - import urldsl.language.PathSegment.simplePathErrorImpl._ + import urldsl.language.simpleErrorImpl._ + + val sampleUrlSegments = UrlStringParserGenerator.defaultUrlStringParserGenerator.parser(sampleUrl).segments + def endOfSegmentsErr(remaining: List[Segment]) = Left(pathError.endOfSegmentRequired(remaining)) "Some matching examples" should "work" in { /** `root` is a "dummy" matcher which matches anything. It returns [[Unit]] when matching. */ - root.matchRawUrl(sampleUrl) should be(Right(())) + root.matchSegments(sampleUrlSegments).map(_.output) should be(Right(())) + root.matchRawUrl(sampleUrl) should be(endOfSegmentsErr(sampleUrlSegments)) + + /** If you want root to match everything, ignoring the remaining segments, you can append a `ignoreRemaining` */ + (root / ignoreRemaining).matchRawUrl(sampleUrl) should be(Right(())) + + /** Alternatively, you can use the `ignoreRemaining` method */ + root.ignoreRemaining.matchRawUrl(sampleUrl) should be(Right(())) + + /** Please note that `ignoreRemaining` will consume segment, so it can only be at the end! */ + (root.ignoreRemaining / "foo").matchRawUrl(sampleUrl) should be(Left(pathError.missingSegment)) /** Appending a specific segment value to the root in order to match the first segment. It returns Unit when * matching, since we assume that the information is already known (it's "foo"). */ - (root / "foo").matchRawUrl(sampleUrl) should be(Right(())) + val foo = root / "foo" + foo.matchSegments(sampleUrlSegments).map(_.output) should be(Right(())) + foo.matchRawUrl(sampleUrl) should be(endOfSegmentsErr(sampleUrlSegments.tail)) + foo.ignoreRemaining.matchRawUrl(sampleUrl) should be(Right(())) /** Appending a [[String]] segmennt in order to retrieve the information contained in the first segment. */ - (root / segment[String]).matchRawUrl(sampleUrl) should be(Right("foo")) + (root / segment[String]).matchSegments(sampleUrlSegments).map(_.output) should be(Right("foo")) + (root / segment[String]).matchRawUrl(sampleUrl) should be(endOfSegmentsErr(sampleUrlSegments.tail)) + (root / segment[String]).ignoreRemaining.matchRawUrl(sampleUrl) should be(Right("foo")) /** You can also directly match other types than [[String]] (if you have the right implicits in scope). */ - (root / "foo" / segment[Int]).matchRawUrl(sampleUrl) should be(Right(23)) - (root / "foo" / 23 / segment[Boolean]).matchRawUrl(sampleUrl) should be(Right(true)) + (foo / segment[Int]).matchSegments(sampleUrlSegments).map(_.output) should be(Right(23)) + (foo / segment[Int]).matchRawUrl(sampleUrl) should be(endOfSegmentsErr(sampleUrlSegments.drop(2))) + (foo / 23 / segment[Boolean]).matchRawUrl(sampleUrl) should be(Right(true)) /** Note that a segment of [[String]] will always manage to match, but of course you get a ... [[String]]. */ - (root / "foo" / segment[String]).matchRawUrl(sampleUrl) should be(Right("23")) + (foo / segment[String]).matchSegments(sampleUrlSegments).map(_.output) should be(Right("23")) + (foo / segment[String]).matchRawUrl(sampleUrl) should be(endOfSegmentsErr(sampleUrlSegments.drop(2))) /** You can of course match several things at once, in which case outputs are "tupled". */ - (root / "foo" / segment[Int] / segment[Boolean]).matchRawUrl(sampleUrl) should be(Right((23, true))) + (foo / segment[Int] / segment[Boolean]).matchRawUrl(sampleUrl) should be(Right((23, true))) (root / segment[String] / segment[Int] / segment[Boolean]).matchRawUrl(sampleUrl) should be( Right(("foo", 23, true)) ) @@ -58,6 +79,7 @@ final class PathSegmentExamples extends AnyFlatSpec with Matchers { (s1 / s2) .as((t: (String, Int)) => Stuff(t._1, t._2), (s: Stuff) => s match { case Stuff(str, j) => (str, j) }) + .ignoreRemaining .matchRawUrl(sampleUrl) should be( Right(Stuff("foo", 23)) ) @@ -66,10 +88,12 @@ final class PathSegmentExamples extends AnyFlatSpec with Matchers { implicit val stuffCodec: Codec[(String, Int), Stuff] = Codec.factory((Stuff.apply _).tupled, { case Stuff(str, j) => (str, j) }) - (s1 / s2).as[Stuff].matchRawUrl(sampleUrl) should be(Right(Stuff("foo", 23))) + (s1 / s2).as[Stuff].ignoreRemaining.matchRawUrl(sampleUrl) should be(Right(Stuff("foo", 23))) /** [[urldsl.language.PathSegment]] can also be filtered to add some more restriction on the matching. */ - s1.filter(_.length > 2, _ => SimplePathMatchingError.SimpleError("too short")).matchRawUrl(sampleUrl) should be( + s1.ignoreRemaining + .filter(_.length > 2, _ => SimplePathMatchingError.SimpleError("too short")) + .matchRawUrl(sampleUrl) should be( Right("foo") ) s1.filter(_.length > 3, _ => SimplePathMatchingError.SimpleError("too short")).matchRawUrl(sampleUrl) should be( @@ -80,12 +104,12 @@ final class PathSegmentExamples extends AnyFlatSpec with Matchers { /** As you probably noticed, [[urldsl.language.PathSegment]] are immutable objects and hence, can be reused freely. */ - (s1 / s1).matchRawUrl(sampleUrl) should be(Right("foo", "23")) + (s1 / s1).ignoreRemaining.matchRawUrl(sampleUrl) should be(Right("foo", "23")) /** You also have the ability to compose [[urldsl.language.PathSegment]] "horizontally", by "branching" several * possibilities */ - (root / (segment[Int] || segment[String])).matchRawUrl(sampleUrl) should be(Right(Right("foo"))) + (root / (segment[Int] || segment[String])).ignoreRemaining.matchRawUrl(sampleUrl) should be(Right(Right("foo"))) /** The [[urldsl.language.PathSegment]] trait is also very general, and while part of its power comes from its great * "composability", it also comes from its ability to make quite generic things. One example is imposing that the diff --git a/url-dsl/src/test/scala/urldsl/examples/QueryParamsExamples.scala b/url-dsl/src/test/scala/urldsl/examples/QueryParamsExamples.scala index 63f8fb6..4d44c46 100644 --- a/url-dsl/src/test/scala/urldsl/examples/QueryParamsExamples.scala +++ b/url-dsl/src/test/scala/urldsl/examples/QueryParamsExamples.scala @@ -66,18 +66,23 @@ final class QueryParamsExamples extends AnyFlatSpec with Matchers { param[Int]("bar").?.matchRawUrl(sampleUrl) should be(Right(None)) param[Int]("empty").?.matchRawUrl(sampleUrl) should be(Right(None)) + case class TooShort() extends IllegalArgumentException("too short") + def tooShort: SimpleParamMatchingError = SimpleParamMatchingError.FromThrowable(TooShort()) + /** [[urldsl.language.QueryParameters]] have a filter method allowing to restrict the things it matches. */ - param[String]("bar").filter(_.length > 3, _ => "too short").matchRawUrl(sampleUrl) should be( + param[String]("bar") + .filter(_.length > 3, _ => tooShort) + .matchRawUrl(sampleUrl) should be( Right( "stuff" ) ) // Note: this is poorly typed as the error type is Any, here. - param[String]("bar").filter(_.length > 10, _ => "too short").matchRawUrl(sampleUrl) should be( - Left( - "too short" - ) + param[String]("bar") + .filter(_.length > 10, _ => tooShort) + .matchRawUrl(sampleUrl) should be( + Left(tooShort) ) /** The filter and ? combinators can be conveniently combined together. This is because ? erases any previously diff --git a/url-dsl/src/test/scala/urldsl/examples/RouterUseCaseExample.scala b/url-dsl/src/test/scala/urldsl/examples/RouterUseCaseExample.scala index 6a2895c..e484a72 100644 --- a/url-dsl/src/test/scala/urldsl/examples/RouterUseCaseExample.scala +++ b/url-dsl/src/test/scala/urldsl/examples/RouterUseCaseExample.scala @@ -2,8 +2,9 @@ package urldsl.examples import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers -import urldsl.language.UrlPart.SimpleUrlPart import urldsl.vocabulary.{PathQueryFragmentMatching, UrlMatching} +import urldsl.language.UrlPart +import urldsl.errors.DummyError /** This class shows a possible implementation of a Router. This could be used in a web frontend (using Scala.js) for * displaying the correct component, or in a web server for triggering the action corresponding to the calling route. @@ -24,6 +25,8 @@ final class RouterUseCaseExample extends AnyFlatSpec with Matchers { import urldsl.language.dummyErrorImpl._ + type SimpleUrlPart[T] = UrlPart[T, _] + /** Link a [[urldsl.language.UrlPart]] matching some routes, to an action using the information extracted from the * route. */ diff --git a/url-dsl/src/test/scala/urldsl/language/AllImplSpec.scala b/url-dsl/src/test/scala/urldsl/language/AllImplSpec.scala index 429ad51..7142f0f 100644 --- a/url-dsl/src/test/scala/urldsl/language/AllImplSpec.scala +++ b/url-dsl/src/test/scala/urldsl/language/AllImplSpec.scala @@ -58,7 +58,7 @@ final class AllImplSpec extends AnyFlatSpec with Matchers { ): Fragment[T, SimpleFragmentMatchingError] = fragment "Implicit conversion" should "be called with the specified error system" in { - askForPath("hey").matchRawUrl(sampleUrl).isRight should be(true) + (root / "hey" / "you" / 23).matchRawUrl(sampleUrl).isRight should be(true) (anySegment / anySegment / askForPath(23)).matchRawUrl(sampleUrl) should be(Right(())) askForFragment("some-fragment").matchRawUrl(sampleUrl).isRight should be(true) (anySegment / anySegment / segment[Int]).withFragment("some-fragment").matchRawUrl(sampleUrl) should be( diff --git a/url-dsl/src/test/scala/urldsl/language/FilteringProperties.scala b/url-dsl/src/test/scala/urldsl/language/FilteringProperties.scala index bd9f414..3dbb1aa 100644 --- a/url-dsl/src/test/scala/urldsl/language/FilteringProperties.scala +++ b/url-dsl/src/test/scala/urldsl/language/FilteringProperties.scala @@ -4,18 +4,22 @@ import org.scalacheck._ import org.scalacheck.Prop._ import urldsl.language.simpleErrorImpl._ import urldsl.vocabulary.{MaybeFragment, Param, Segment} +import urldsl.errors.SimpleParamMatchingError object FilteringProperties extends Properties("Filtering") { def predicate(x: Int): Boolean = x % 2 == 0 property("Filtering segment on even integers") = forAll(Gen.posNum[Int]) { (number: Int) => - segment[Int].filter(predicate, _ => ()).matchSegments(List(Segment(number.toString))).isRight == predicate(number) + segment[Int] + .filter(predicate, _ => pathError.unit) + .matchSegments(List(Segment(number.toString))) + .isRight == predicate(number) } property("Filtering query param on event integers") = forAll(Gen.posNum[Int]) { (number: Int) => param[Int]("num") - .filter(predicate, _ => ()) + .filter(predicate, x => SimpleParamMatchingError.FromThrowable(new IllegalArgumentException(s"$x is not even!"))) .matchParams(Map("num" -> Param(List(number.toString)))) .isRight == predicate(number) } diff --git a/url-dsl/src/test/scala/urldsl/language/FragmentSpec.scala b/url-dsl/src/test/scala/urldsl/language/FragmentSpec.scala index 8229196..9b7c716 100644 --- a/url-dsl/src/test/scala/urldsl/language/FragmentSpec.scala +++ b/url-dsl/src/test/scala/urldsl/language/FragmentSpec.scala @@ -8,7 +8,7 @@ import urldsl.vocabulary.{FromString, MaybeFragment} final class FragmentSpec extends munit.FunSuite { - val fragment: Fragment[Unit, Any] = 5 + val fragment: Fragment[Unit, SimpleFragmentMatchingError] = 5 val url = "http://localhost/#5" val error: FragmentMatchingError[SimpleFragmentMatchingError] = SimpleFragmentMatchingError.itIsFragmentMatchingError diff --git a/url-dsl/src/test/scala/urldsl/language/PathSegmentProperties.scala b/url-dsl/src/test/scala/urldsl/language/PathSegmentProperties.scala index e3143e0..e69e4a1 100644 --- a/url-dsl/src/test/scala/urldsl/language/PathSegmentProperties.scala +++ b/url-dsl/src/test/scala/urldsl/language/PathSegmentProperties.scala @@ -181,14 +181,14 @@ abstract class PathSegmentProperties[E](val impl: PathSegmentImpl[E], val error: } property("provide method decodes and encodes x") = forAll { (x: Int) => - val path = segment[Int].provide(x)(error, Printer[Int]) + val path = segment[Int].provide(x) Prop(path.matchSegments(List(Segment(x.toString))) == Right(PathMatchOutput((), Nil))) && Prop( path.createPath() == x.toString ) } property("provide method fails when decoding wrong value") = forAll { (x: Int, y: Int) => - val path = segment[Int].provide(x)(error, Printer[Int]) + val path = segment[Int].provide(x) Prop(x == y) || Prop( path.matchSegments(List(Segment(y.toString))) == Left(error.wrongValue(x.toString, y.toString)) )