diff --git a/tests/shared/src/test/scala/zio/schema/DynamicValueGen.scala b/tests/shared/src/test/scala/zio/schema/DynamicValueGen.scala index 0a311fae7..4b98751fa 100644 --- a/tests/shared/src/test/scala/zio/schema/DynamicValueGen.scala +++ b/tests/shared/src/test/scala/zio/schema/DynamicValueGen.scala @@ -45,6 +45,31 @@ object DynamicValueGen { } } + // Method to generate arbitrary DynamicValue + def anyDynamicValue: Gen[Sized, DynamicValue] = + Gen.oneOf( + anyPrimitiveDynamicValue(StandardType.StringType), + anyPrimitiveDynamicValue(StandardType.IntType), + anyPrimitiveDynamicValue(StandardType.BoolType), + anyPrimitiveDynamicValue(StandardType.DoubleType), + anyPrimitiveDynamicValue(StandardType.UUIDType), + anyPrimitiveDynamicValue(StandardType.LocalDateType), + anyPrimitiveDynamicValue(StandardType.LocalDateTimeType), + anyPrimitiveDynamicValue(StandardType.LocalTimeType), + anyPrimitiveDynamicValue(StandardType.InstantType), + anyPrimitiveDynamicValue(StandardType.DurationType), + anyPrimitiveDynamicValue(StandardType.ZoneIdType), + anyPrimitiveDynamicValue(StandardType.ZoneOffsetType), + anyPrimitiveDynamicValue(StandardType.ZonedDateTimeType), + anyPrimitiveDynamicValue(StandardType.YearType), + anyPrimitiveDynamicValue(StandardType.YearMonthType), + anyPrimitiveDynamicValue(StandardType.MonthType), + anyPrimitiveDynamicValue(StandardType.MonthDayType), + anyPrimitiveDynamicValue(StandardType.DayOfWeekType), + anyPrimitiveDynamicValue(StandardType.BigDecimalType), + anyPrimitiveDynamicValue(StandardType.BigIntegerType) + ) + //scalafmt: { maxColumn = 400 } def anyDynamicValueOfSchema[A](schema: Schema[A]): Gen[Sized, DynamicValue] = schema match { diff --git a/tests/shared/src/test/scala/zio/schema/DynamicValueSpec.scala b/tests/shared/src/test/scala/zio/schema/DynamicValueSpec.scala index 26f91be2d..a4f6df7b2 100644 --- a/tests/shared/src/test/scala/zio/schema/DynamicValueSpec.scala +++ b/tests/shared/src/test/scala/zio/schema/DynamicValueSpec.scala @@ -100,6 +100,22 @@ object DynamicValueSpec extends ZIOSpecDefault { assertTrue(json2 == Right(json)) } } @@ TestAspect.size(250) @@ TestAspect.ignore + ), + suite("hashCode and equality consistency")( + test("hashCode does not change") { + check(DynamicValueGen.anyDynamicValue) { dynamicValue => + val hash1 = dynamicValue.hashCode() + val hash2 = dynamicValue.hashCode() + assert(hash1)(equalTo(hash2)) + } + }, + test("equivalent DynamicValues have same hashCode and equality") { + check(DynamicValueGen.anyDynamicValue) { dynamicValue => + val dynamicValueCopy = dynamicValue + assert(dynamicValue.hashCode())(equalTo(dynamicValueCopy.hashCode())) && + assert(dynamicValue)(equalTo(dynamicValueCopy)) + } + } ) ) @@ -114,5 +130,4 @@ object DynamicValueSpec extends ZIOSpecDefault { check(gen) { a => assert(schema.fromDynamic(schema.toDynamic(a)))(isRight(equalTo(a))) } - } diff --git a/zio-schema/shared/src/main/scala/zio/schema/DynamicValue.scala b/zio-schema/shared/src/main/scala/zio/schema/DynamicValue.scala index 75289f3e0..0414b3768 100644 --- a/zio-schema/shared/src/main/scala/zio/schema/DynamicValue.scala +++ b/zio-schema/shared/src/main/scala/zio/schema/DynamicValue.scala @@ -118,6 +118,59 @@ sealed trait DynamicValue { case _ => Left(DecodeError.CastError(self, schema)) } + override def hashCode(): Int = this match { + case DynamicValue.Primitive(value, standardType) => 31 * value.hashCode() + standardType.hashCode() + case DynamicValue.Record(id, values) => 31 * id.hashCode() + values.hashCode() + case DynamicValue.Enumeration(id, value) => 31 * id.hashCode() + value.hashCode() + case DynamicValue.Sequence(values) => values.hashCode() + case DynamicValue.Dictionary(entries) => entries.hashCode() + case DynamicValue.SetValue(values) => values.hashCode() + case DynamicValue.SomeValue(value) => value.hashCode() + case DynamicValue.NoneValue => 0 + case DynamicValue.Tuple(left, right) => 31 * left.hashCode() + right.hashCode() + case DynamicValue.LeftValue(value) => value.hashCode() + case DynamicValue.RightValue(value) => value.hashCode() + case DynamicValue.BothValue(left, right) => 31 * left.hashCode() + right.hashCode() + case DynamicValue.DynamicAst(ast) => ast.hashCode() + case DynamicValue.Error(message) => message.hashCode() + case DynamicValue.Singleton(instance) => instance.hashCode() + } + override def equals(obj: Any): Boolean = obj match { + case that: DynamicValue if this eq that => true + case that: DynamicValue => + (this, that) match { + case (DynamicValue.Primitive(value1, standardType1), DynamicValue.Primitive(value2, standardType2)) => + value1 == value2 && standardType1 == standardType2 + case (DynamicValue.Record(id1, values1), DynamicValue.Record(id2, values2)) => + id1 == id2 && values1 == values2 + case (DynamicValue.Enumeration(id1, value1), DynamicValue.Enumeration(id2, value2)) => + id1 == id2 && value1 == value2 + case (DynamicValue.Sequence(values1), DynamicValue.Sequence(values2)) => + values1 == values2 + case (DynamicValue.Dictionary(entries1), DynamicValue.Dictionary(entries2)) => + entries1 == entries2 + case (DynamicValue.SetValue(values1), DynamicValue.SetValue(values2)) => + values1 == values2 + case (DynamicValue.SomeValue(value1), DynamicValue.SomeValue(value2)) => + value1 == value2 + case (DynamicValue.Tuple(left1, right1), DynamicValue.Tuple(left2, right2)) => + left1 == left2 && right1 == right2 + case (DynamicValue.LeftValue(value1), DynamicValue.LeftValue(value2)) => + value1 == value2 + case (DynamicValue.RightValue(value1), DynamicValue.RightValue(value2)) => + value1 == value2 + case (DynamicValue.BothValue(left1, right1), DynamicValue.BothValue(left2, right2)) => + left1 == left2 && right1 == right2 + case (DynamicValue.DynamicAst(ast1), DynamicValue.DynamicAst(ast2)) => + ast1 == ast2 + case (DynamicValue.Error(message1), DynamicValue.Error(message2)) => + message1 == message2 + case (DynamicValue.Singleton(instance1), DynamicValue.Singleton(instance2)) => + instance1 == instance2 + case _ => false + } + case _ => false + } }