Skip to content

Commit

Permalink
Introduce the notion of HasLegalValues and HasIllegalValues
Browse files Browse the repository at this point in the history
  • Loading branch information
nrinaudo committed Apr 24, 2020
1 parent 407c222 commit a96657c
Show file tree
Hide file tree
Showing 25 changed files with 408 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,21 @@ package kantan.codecs.strings

import java.text.SimpleDateFormat
import java.util.{Date, Locale}
import kantan.codecs.laws.{HasIllegalValues, HasLegalValues}
import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class DateCodecTests extends DisciplineSuite {
val format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSz", Locale.ENGLISH)

implicit val codec: StringCodec[Date] =
StringCodec.dateCodec(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSz", Locale.ENGLISH))
StringCodec.dateCodec(format)

implicit val hasLegalValues: HasLegalValues[String, Date, codecs.type] =
HasLegalValues.from(date => format.synchronized(format.format(date)))

implicit val hasIllegalValues: HasIllegalValues[String, Date, codecs.type] =
HasIllegalValues.fromUnsafeString(s => format.synchronized(format.parse(s)))

checkAll("StringDecoder[Date]", StringCodecTests[Date].decoder[Int, Int])
checkAll("StringEncoder[Date]", StringCodecTests[Date].encoder[Int, Int])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class BigDecimalCodecTests extends DisciplineSuite {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class BigIntCodecTests extends DisciplineSuite {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class BooleanCodecTests extends DisciplineSuite {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class ByteCodecTests extends DisciplineSuite {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class CharCodecTests extends DisciplineSuite {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class DoubleCodecTests extends DisciplineSuite {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringDecoderTests, StringEncoderTests}
import kantan.codecs.laws.discipline.arbitrary._
import kantan.codecs.strings.tagged._

class EitherCodecTests extends DisciplineSuite {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class FloatCodecTests extends DisciplineSuite {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class IntCodecTests extends DisciplineSuite {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class LongCodecTests extends DisciplineSuite {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringDecoderTests, StringEncoderTests}
import kantan.codecs.laws.discipline.arbitrary._
import kantan.codecs.strings.tagged._

class OptionCodecTests extends DisciplineSuite {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class ShortCodecTests extends DisciplineSuite {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package kantan.codecs.strings

import kantan.codecs.laws.discipline.{DisciplineSuite, StringCodecTests}
import kantan.codecs.laws.discipline.arbitrary._

class StringsCodecTests extends DisciplineSuite {

Expand Down
23 changes: 16 additions & 7 deletions core/shared/src/test/scala/kantan/codecs/strings/tagged.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package kantan.codecs.strings
import imp.imp
import kantan.codecs.{Codec, Decoder, Encoder}
import kantan.codecs.laws.{DecoderLaws, EncoderLaws}
import kantan.codecs.laws.{HasIllegalStringValues, HasIllegalValues, HasLegalStringValues, HasLegalValues}
import kantan.codecs.laws.CodecValue.LegalValue
import kantan.codecs.laws.discipline.{DecoderTests => RootDecoderTests, EncoderTests => RootEncoderTests}
import kantan.codecs.laws.discipline.arbitrary._
Expand All @@ -32,13 +33,15 @@ object tagged {

// - Type aliases for readability ------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------------------------
type DecoderTests[D] = RootDecoderTests[String, D, DecodeError, tagged.type]
type EncoderTests[D] = RootEncoderTests[String, D, tagged.type]
type TaggedDecoder[D] = Decoder[String, D, DecodeError, tagged.type]
type TaggedEncoder[D] = Encoder[String, D, tagged.type]
type TaggedDecoderLaws[D] = DecoderLaws[String, D, DecodeError, tagged.type]
type TaggedEncoderLaws[D] = EncoderLaws[String, D, tagged.type]
type TaggedLegalValue[D] = LegalValue[String, D, tagged.type]
type DecoderTests[D] = RootDecoderTests[String, D, DecodeError, tagged.type]
type EncoderTests[D] = RootEncoderTests[String, D, tagged.type]
type TaggedDecoder[D] = Decoder[String, D, DecodeError, tagged.type]
type TaggedEncoder[D] = Encoder[String, D, tagged.type]
type TaggedDecoderLaws[D] = DecoderLaws[String, D, DecodeError, tagged.type]
type TaggedEncoderLaws[D] = EncoderLaws[String, D, tagged.type]
type TaggedLegalValue[D] = LegalValue[String, D, tagged.type]
type HasLegalTaggedValues[D] = HasLegalValues[String, D, tagged.type]
type HasIllegalTaggedValues[D] = HasIllegalValues[String, D, tagged.type]

// - Specialised tests -----------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -76,4 +79,10 @@ object tagged {
def codec[D: StringCodec]: Codec[String, D, DecodeError, tagged.type] =
imp[StringCodec[D]].tag[tagged.type]

implicit def hasLegalValues[D](implicit hlv: HasLegalStringValues[D]): HasLegalTaggedValues[D] =
hlv.tag[tagged.type]

implicit def hasIllegalValues[D](implicit hiv: HasIllegalStringValues[D]): HasIllegalTaggedValues[D] =
hiv.tag[tagged.type]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2016 Nicolas Rinaudo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package kantan.codecs.laws

trait PlatformSpecificHasIllegalValues
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2016 Nicolas Rinaudo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package kantan.codecs.laws

trait PlatformSpecificHasLegalValues
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2016 Nicolas Rinaudo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package kantan.codecs.laws

import java.io.File
import java.net.URL
import java.nio.file.{Path, Paths}
import kantan.codecs.laws.HasIllegalValues._

trait PlatformSpecificHasIllegalValues {

implicit val url: HasIllegalStringValues[URL] = fromUnsafeString(e => new URL(e))
implicit val path: HasIllegalStringValues[Path] = fromUnsafeString(e => Paths.get(e.toString))
implicit val file: HasIllegalStringValues[File] = fromUnsafeString(e => new File(e))

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2016 Nicolas Rinaudo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package kantan.codecs.laws

import java.io.File
import java.net.URL
import java.nio.file.Path
import kantan.codecs.laws.HasLegalValues._

trait PlatformSpecificHasLegalValues {

implicit val url: HasLegalStringValues[URL] = fromToString
implicit val path: HasLegalStringValues[Path] = fromToString
implicit val file: HasLegalStringValues[File] = fromToString

}
47 changes: 40 additions & 7 deletions laws/shared/src/main/scala/kantan/codecs/laws/CodecValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,66 @@

package kantan.codecs.laws

// TODO: investigate what type variance annotations can be usefully applied to CodecValue.
import imp.imp
import org.scalacheck.{Arbitrary, Gen, Shrink}

/** Represents possible encoded and decoded values.
*
* There are two main categories of values:
* - legal ones: an encoded / decoded couple, where decoding one should yield the other and vice versa.
* - illegal ones: an encoded value that cannot be decoded.
*
* The purpose of this type is to test encoder, decoder and codec laws - which means we'll almost always need
* `Arbitrary` instances for them. These can be tedious to write, but that tedium can be alleviated somewhat through
* the [[HasIllegalValues]] and [[HasLegalValues]] type classes.
*/
sealed abstract class CodecValue[Encoded, Decoded, Tag] extends Product with Serializable {
def encoded: Encoded
def mapEncoded[E](f: Encoded => E): CodecValue[E, Decoded, Tag]
def mapDecoded[D](f: Decoded => D): CodecValue[Encoded, D, Tag]
def tag[T]: CodecValue[Encoded, Decoded, T]

def isLegal: Boolean
def isIllegal: Boolean = !isLegal
def tag[T]: CodecValue[Encoded, Decoded, T]
}

object CodecValue {

/** Represents a legal value: one that can be safely encoded and decoded. */
final case class LegalValue[Encoded, Decoded, Tag](encoded: Encoded, decoded: Decoded)
extends CodecValue[Encoded, Decoded, Tag] {

override def mapDecoded[D](f: Decoded => D) = LegalValue(encoded, f(decoded))
override def mapEncoded[E](f: Encoded => E) = LegalValue(f(encoded), decoded)
override val isLegal = true
@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf"))
override def tag[T] = this.asInstanceOf[LegalValue[Encoded, Decoded, T]]
override val isLegal = true
override def tag[T]: LegalValue[Encoded, Decoded, T] = this.asInstanceOf[LegalValue[Encoded, Decoded, T]]
}

/** Represents an illegal value: one that cannot be decoded. */
final case class IllegalValue[Encoded, Decoded, Tag](encoded: Encoded) extends CodecValue[Encoded, Decoded, Tag] {
override def mapDecoded[D](f: Decoded => D) = IllegalValue(encoded)
override def mapEncoded[E](f: Encoded => E) = IllegalValue(f(encoded))
override val isLegal = false
@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf"))
override def tag[T] = this.asInstanceOf[IllegalValue[Encoded, Decoded, T]]
override val isLegal = false
override def tag[T]: IllegalValue[Encoded, Decoded, T] = this.asInstanceOf[IllegalValue[Encoded, Decoded, T]]
}

// - Arbitrary instances ---------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------------------------
implicit def arbValue[E, D, T](
implicit arbLegal: Arbitrary[LegalValue[E, D, T]],
arbIllegal: Arbitrary[IllegalValue[E, D, T]]
): Arbitrary[CodecValue[E, D, T]] =
Arbitrary(Gen.oneOf(arbLegal.arbitrary, arbIllegal.arbitrary))

implicit def arbLegalValue[E, D: Arbitrary, T](
implicit alv: HasLegalValues[E, D, T]
): Arbitrary[LegalValue[E, D, T]] =
Arbitrary(imp[Arbitrary[D]].arbitrary.map(alv.asLegalValue))

implicit def arbIllegalValue[E: Arbitrary, D, T](
implicit aiv: HasIllegalValues[E, D, T]
): Arbitrary[IllegalValue[E, D, T]] =
Arbitrary(imp[Arbitrary[E]].arbitrary.map(aiv.asIllegalValue))

}
Loading

0 comments on commit a96657c

Please sign in to comment.