diff --git a/build.sbt b/build.sbt index 5e058b0613..6bbdb57f1f 100644 --- a/build.sbt +++ b/build.sbt @@ -303,6 +303,8 @@ lazy val core = projectMatrix ProblemFilters.exclude[MissingClassProblem]("alloy.proto.ProtoCompactOffsetDateTime"), // originating in an Alloy update that removed ProtoCompactOffsetDateTime ProblemFilters.exclude[MissingClassProblem]("alloy.proto.ProtoCompactOffsetDateTime$"), + // private class that got removed + ProblemFilters.exclude[MissingClassProblem]("smithy4s.Validator$ValidatorImpl") ) ) .jvmPlatform(allJvmScalaVersions, jvmDimSettings) diff --git a/modules/bootstrapped/src/generated/smithy4s/example/AccountId.scala b/modules/bootstrapped/src/generated/smithy4s/example/AccountId.scala new file mode 100644 index 0000000000..8bb0916e90 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/AccountId.scala @@ -0,0 +1,18 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.schema.Schema.string + +object AccountId extends ValidatedNewtype[String] { + val id: ShapeId = ShapeId("smithy4s.example", "AccountId") + val hints: Hints = Hints.empty + val underlyingSchema: Schema[String] = string.withId(id).addHints(hints).validated(smithy.api.Pattern("[a-zA-Z0-9]+")) + val validator: Validator[String, AccountId] = Validator.Builder.simple[String].validating(smithy.api.Pattern("[a-zA-Z0-9]+")).biject(Bijection[String, AccountId](_.asInstanceOf[AccountId], value(_))).build() + implicit val schema: Schema[AccountId] = validator.toSchema(underlyingSchema) + @inline def apply(a: String): Either[String, AccountId] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/DeviceId.scala b/modules/bootstrapped/src/generated/smithy4s/example/DeviceId.scala new file mode 100644 index 0000000000..6dd8496c39 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/DeviceId.scala @@ -0,0 +1,18 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.schema.Schema.string + +object DeviceId extends ValidatedNewtype[String] { + val id: ShapeId = ShapeId("smithy4s.example", "DeviceId") + val hints: Hints = Hints.empty + val underlyingSchema: Schema[String] = string.withId(id).addHints(hints).validated(smithy.api.Length(min = Some(1L), max = None)) + val validator: Validator[String, DeviceId] = Validator.Builder.simple[String].validating(smithy.api.Length(min = Some(1L), max = None)).biject(Bijection[String, DeviceId](_.asInstanceOf[DeviceId], value(_))).build() + implicit val schema: Schema[DeviceId] = validator.toSchema(underlyingSchema) + @inline def apply(a: String): Either[String, DeviceId] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedIndexedSeqConstrainedMember.scala b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedIndexedSeqConstrainedMember.scala new file mode 100644 index 0000000000..c260f52a6e --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedIndexedSeqConstrainedMember.scala @@ -0,0 +1,19 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.schema.Schema.indexedSeq +import smithy4s.schema.Schema.string + +object ValidatedConstrainedIndexedSeqConstrainedMember extends ValidatedNewtype[IndexedSeq[String]] { + val id: ShapeId = ShapeId("smithy4s.example", "ValidatedConstrainedIndexedSeqConstrainedMember") + val hints: Hints = Hints.empty + val underlyingSchema: Schema[IndexedSeq[String]] = indexedSeq(string.addMemberHints().validated(smithy.api.Length(min = None, max = Some(2L)))).withId(id).addHints(hints).validated(smithy.api.Length(min = None, max = Some(1L))) + val validator: Validator[IndexedSeq[String], ValidatedConstrainedIndexedSeqConstrainedMember] = Validator.Builder.indexedSeq[String].validating(smithy.api.Length(min = None, max = Some(1L))).validatingMember(smithy.api.Length(min = None, max = Some(2L))).biject(Bijection[IndexedSeq[String], ValidatedConstrainedIndexedSeqConstrainedMember](_.asInstanceOf[ValidatedConstrainedIndexedSeqConstrainedMember], value(_))).build() + implicit val schema: Schema[ValidatedConstrainedIndexedSeqConstrainedMember] = validator.toSchema(underlyingSchema) + @inline def apply(a: IndexedSeq[String]): Either[String, ValidatedConstrainedIndexedSeqConstrainedMember] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedList.scala b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedList.scala new file mode 100644 index 0000000000..a3bb4533da --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedList.scala @@ -0,0 +1,19 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.schema.Schema.list +import smithy4s.schema.Schema.string + +object ValidatedConstrainedList extends ValidatedNewtype[List[String]] { + val id: ShapeId = ShapeId("smithy4s.example", "ValidatedConstrainedList") + val hints: Hints = Hints.empty + val underlyingSchema: Schema[List[String]] = list(string).withId(id).addHints(hints).validated(smithy.api.Length(min = None, max = Some(1L))) + val validator: Validator[List[String], ValidatedConstrainedList] = Validator.Builder.list[String].validating(smithy.api.Length(min = None, max = Some(1L))).biject(Bijection[List[String], ValidatedConstrainedList](_.asInstanceOf[ValidatedConstrainedList], value(_))).build() + implicit val schema: Schema[ValidatedConstrainedList] = validator.toSchema(underlyingSchema) + @inline def apply(a: List[String]): Either[String, ValidatedConstrainedList] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedListRefinedMember.scala b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedListRefinedMember.scala new file mode 100644 index 0000000000..a503b8ab1c --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedListRefinedMember.scala @@ -0,0 +1,18 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.schema.Schema.list + +object ValidatedConstrainedListRefinedMember extends ValidatedNewtype[List[Name]] { + val id: ShapeId = ShapeId("smithy4s.example", "ValidatedConstrainedListRefinedMember") + val hints: Hints = Hints.empty + val underlyingSchema: Schema[List[Name]] = list(Name.schema).withId(id).addHints(hints).validated(smithy.api.Length(min = None, max = Some(1L))) + val validator: Validator[List[Name], ValidatedConstrainedListRefinedMember] = Validator.Builder.list[Name].validating(smithy.api.Length(min = None, max = Some(1L))).biject(Bijection[List[Name], ValidatedConstrainedListRefinedMember](_.asInstanceOf[ValidatedConstrainedListRefinedMember], value(_))).build() + implicit val schema: Schema[ValidatedConstrainedListRefinedMember] = validator.toSchema(underlyingSchema) + @inline def apply(a: List[Name]): Either[String, ValidatedConstrainedListRefinedMember] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedMap.scala b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedMap.scala new file mode 100644 index 0000000000..ef430de8b2 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedMap.scala @@ -0,0 +1,20 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.schema.Schema.int +import smithy4s.schema.Schema.map +import smithy4s.schema.Schema.string + +object ValidatedConstrainedMap extends ValidatedNewtype[Map[String, Int]] { + val id: ShapeId = ShapeId("smithy4s.example", "ValidatedConstrainedMap") + val hints: Hints = Hints.empty + val underlyingSchema: Schema[Map[String, Int]] = map(string, int).withId(id).addHints(hints).validated(smithy.api.Length(min = None, max = Some(1L))) + val validator: Validator[Map[String, Int], ValidatedConstrainedMap] = Validator.Builder.map[String, Int].validating(smithy.api.Length(min = None, max = Some(1L))).biject(Bijection[Map[String, Int], ValidatedConstrainedMap](_.asInstanceOf[ValidatedConstrainedMap], value(_))).build() + implicit val schema: Schema[ValidatedConstrainedMap] = validator.toSchema(underlyingSchema) + @inline def apply(a: Map[String, Int]): Either[String, ValidatedConstrainedMap] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedVectorRefinedConstrainedMember.scala b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedVectorRefinedConstrainedMember.scala new file mode 100644 index 0000000000..e6f738fa27 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedConstrainedVectorRefinedConstrainedMember.scala @@ -0,0 +1,20 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.example.instances._ +import smithy4s.schema.Schema.vector + + +object ValidatedConstrainedVectorRefinedConstrainedMember extends ValidatedNewtype[Vector[Name]] { + val id: ShapeId = ShapeId("smithy4s.example", "ValidatedConstrainedVectorRefinedConstrainedMember") + val hints: Hints = Hints.empty + val underlyingSchema: Schema[Vector[Name]] = vector(Name.schema.addMemberHints().validated(smithy.api.Length(min = None, max = Some(2L)))).withId(id).addHints(hints).validated(smithy.api.Length(min = None, max = Some(1L))) + val validator: Validator[Vector[Name], ValidatedConstrainedVectorRefinedConstrainedMember] = Validator.Builder.vector[Name].validating(smithy.api.Length(min = None, max = Some(1L))).validatingMember(smithy.api.Length(min = None, max = Some(2L))).biject(Bijection[Vector[Name], ValidatedConstrainedVectorRefinedConstrainedMember](_.asInstanceOf[ValidatedConstrainedVectorRefinedConstrainedMember], value(_))).build() + implicit val schema: Schema[ValidatedConstrainedVectorRefinedConstrainedMember] = validator.toSchema(underlyingSchema) + @inline def apply(a: Vector[Name]): Either[String, ValidatedConstrainedVectorRefinedConstrainedMember] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedMapConstrainedKey.scala b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedMapConstrainedKey.scala new file mode 100644 index 0000000000..01861c95f6 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedMapConstrainedKey.scala @@ -0,0 +1,20 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.schema.Schema.int +import smithy4s.schema.Schema.map +import smithy4s.schema.Schema.string + +object ValidatedMapConstrainedKey extends ValidatedNewtype[Map[String, Int]] { + val id: ShapeId = ShapeId("smithy4s.example", "ValidatedMapConstrainedKey") + val hints: Hints = Hints.empty + val underlyingSchema: Schema[Map[String, Int]] = map(string.addMemberHints().validated(smithy.api.Length(min = None, max = Some(2L))), int).withId(id).addHints(hints) + val validator: Validator[Map[String, Int], ValidatedMapConstrainedKey] = Validator.Builder.map[String, Int].validatingKey(smithy.api.Length(min = None, max = Some(2L))).biject(Bijection[Map[String, Int], ValidatedMapConstrainedKey](_.asInstanceOf[ValidatedMapConstrainedKey], value(_))).build() + implicit val schema: Schema[ValidatedMapConstrainedKey] = validator.toSchema(underlyingSchema) + @inline def apply(a: Map[String, Int]): Either[String, ValidatedMapConstrainedKey] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedMapConstrainedValue.scala b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedMapConstrainedValue.scala new file mode 100644 index 0000000000..d180c539e1 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedMapConstrainedValue.scala @@ -0,0 +1,19 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.schema.Schema.map +import smithy4s.schema.Schema.string + +object ValidatedMapConstrainedValue extends ValidatedNewtype[Map[String, String]] { + val id: ShapeId = ShapeId("smithy4s.example", "ValidatedMapConstrainedValue") + val hints: Hints = Hints.empty + val underlyingSchema: Schema[Map[String, String]] = map(string, string.addMemberHints().validated(smithy.api.Pattern(s"^[a-zA-Z0-9]+$$"))).withId(id).addHints(hints) + val validator: Validator[Map[String, String], ValidatedMapConstrainedValue] = Validator.Builder.map[String, String].validatingValue(smithy.api.Pattern(s"^[a-zA-Z0-9]+$$")).biject(Bijection[Map[String, String], ValidatedMapConstrainedValue](_.asInstanceOf[ValidatedMapConstrainedValue], value(_))).build() + implicit val schema: Schema[ValidatedMapConstrainedValue] = validator.toSchema(underlyingSchema) + @inline def apply(a: Map[String, String]): Either[String, ValidatedMapConstrainedValue] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedRefinedList.scala b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedRefinedList.scala new file mode 100644 index 0000000000..d637a34828 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedRefinedList.scala @@ -0,0 +1,22 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.refined.NonEmptyList +import smithy4s.schema.Schema.list +import smithy4s.schema.Schema.string + +object ValidatedRefinedList extends ValidatedNewtype[NonEmptyList[String]] { + val id: ShapeId = ShapeId("smithy4s.example", "ValidatedRefinedList") + val hints: Hints = Hints( + smithy4s.example.NonEmptyListFormat(), + ).lazily + val underlyingSchema: Schema[NonEmptyList[String]] = list(string).refined[NonEmptyList[String]](smithy4s.example.NonEmptyListFormat()).withId(id).addHints(hints) + val validator: Validator[NonEmptyList[String], ValidatedRefinedList] = Validator.Builder.list[String].refined[NonEmptyList[String]](smithy4s.example.NonEmptyListFormat()).biject(Bijection[NonEmptyList[String], ValidatedRefinedList](_.asInstanceOf[ValidatedRefinedList], value(_))).build() + implicit val schema: Schema[ValidatedRefinedList] = validator.toSchema(underlyingSchema) + @inline def apply(a: NonEmptyList[String]): Either[String, ValidatedRefinedList] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedRefinedListConstrainedMember.scala b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedRefinedListConstrainedMember.scala new file mode 100644 index 0000000000..8048a9deb8 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedRefinedListConstrainedMember.scala @@ -0,0 +1,22 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.refined.NonEmptyList +import smithy4s.schema.Schema.list +import smithy4s.schema.Schema.string + +object ValidatedRefinedListConstrainedMember extends ValidatedNewtype[NonEmptyList[String]] { + val id: ShapeId = ShapeId("smithy4s.example", "ValidatedRefinedListConstrainedMember") + val hints: Hints = Hints( + smithy4s.example.NonEmptyListFormat(), + ).lazily + val underlyingSchema: Schema[NonEmptyList[String]] = list(string.addMemberHints().validated(smithy.api.Length(min = None, max = Some(2L)))).refined[NonEmptyList[String]](smithy4s.example.NonEmptyListFormat()).withId(id).addHints(hints) + val validator: Validator[NonEmptyList[String], ValidatedRefinedListConstrainedMember] = Validator.Builder.list[String].validatingMember(smithy.api.Length(min = None, max = Some(2L))).refined[NonEmptyList[String]](smithy4s.example.NonEmptyListFormat()).biject(Bijection[NonEmptyList[String], ValidatedRefinedListConstrainedMember](_.asInstanceOf[ValidatedRefinedListConstrainedMember], value(_))).build() + implicit val schema: Schema[ValidatedRefinedListConstrainedMember] = validator.toSchema(underlyingSchema) + @inline def apply(a: NonEmptyList[String]): Either[String, ValidatedRefinedListConstrainedMember] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedSetConstrainedMember.scala b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedSetConstrainedMember.scala new file mode 100644 index 0000000000..3bdc2492f2 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedSetConstrainedMember.scala @@ -0,0 +1,21 @@ +package smithy4s.example + +import smithy4s.Bijection +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ValidatedNewtype +import smithy4s.Validator +import smithy4s.schema.Schema.set +import smithy4s.schema.Schema.string + +object ValidatedSetConstrainedMember extends ValidatedNewtype[Set[String]] { + val id: ShapeId = ShapeId("smithy4s.example", "ValidatedSetConstrainedMember") + val hints: Hints = Hints( + smithy.api.UniqueItems(), + ).lazily + val underlyingSchema: Schema[Set[String]] = set(string.addMemberHints().validated(smithy.api.Length(min = None, max = Some(2L)))).withId(id).addHints(hints) + val validator: Validator[Set[String], ValidatedSetConstrainedMember] = Validator.Builder.set[String].validatingMember(smithy.api.Length(min = None, max = Some(2L))).biject(Bijection[Set[String], ValidatedSetConstrainedMember](_.asInstanceOf[ValidatedSetConstrainedMember], value(_))).build() + implicit val schema: Schema[ValidatedSetConstrainedMember] = validator.toSchema(underlyingSchema) + @inline def apply(a: Set[String]): Either[String, ValidatedSetConstrainedMember] = validator.validate(a) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedString.scala b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedString.scala index 1e5f93e27d..95a7cce08b 100644 --- a/modules/bootstrapped/src/generated/smithy4s/example/ValidatedString.scala +++ b/modules/bootstrapped/src/generated/smithy4s/example/ValidatedString.scala @@ -12,7 +12,7 @@ object ValidatedString extends ValidatedNewtype[String] { val id: ShapeId = ShapeId("smithy4s.example", "ValidatedString") val hints: Hints = Hints.empty val underlyingSchema: Schema[String] = string.withId(id).addHints(hints).validated(smithy.api.Length(min = Some(1L), max = None)).validated(smithy.api.Pattern("[a-zA-Z0-9]+")) - val validator: Validator[String, ValidatedString] = Validator.of[String, ValidatedString](Bijection[String, ValidatedString](_.asInstanceOf[ValidatedString], value(_))).validating(smithy.api.Length(min = Some(1L), max = None)).alsoValidating(smithy.api.Pattern("[a-zA-Z0-9]+")) + val validator: Validator[String, ValidatedString] = Validator.Builder.simple[String].validating(smithy.api.Length(min = Some(1L), max = None)).validating(smithy.api.Pattern("[a-zA-Z0-9]+")).biject(Bijection[String, ValidatedString](_.asInstanceOf[ValidatedString], value(_))).build() implicit val schema: Schema[ValidatedString] = validator.toSchema(underlyingSchema) @inline def apply(a: String): Either[String, ValidatedString] = validator.validate(a) } diff --git a/modules/bootstrapped/src/generated/smithy4s/example/package.scala b/modules/bootstrapped/src/generated/smithy4s/example/package.scala index 536149eca0..eb5e356a84 100644 --- a/modules/bootstrapped/src/generated/smithy4s/example/package.scala +++ b/modules/bootstrapped/src/generated/smithy4s/example/package.scala @@ -43,6 +43,7 @@ package object example { /** This is a simple example of a "quoted string" */ type AString = smithy4s.example.AString.Type + type AccountId = smithy4s.example.AccountId.Type type AdditionalProperties = smithy4s.example.AdditionalProperties.Type type Age = smithy4s.example.Age.Type /** Multiple line doc comment for another string @@ -62,6 +63,7 @@ package object example { type DefaultStringMap = smithy4s.example.DefaultStringMap.Type @deprecated(message = "N/A", since = "N/A") type DeprecatedString = smithy4s.example.DeprecatedString.Type + type DeviceId = smithy4s.example.DeviceId.Type type DogName = smithy4s.example.DogName.Type type ExtraData = smithy4s.example.ExtraData.Type type FancyList = smithy4s.example.FancyList.Type @@ -127,6 +129,16 @@ package object example { type UnwrappedFancyList = smithy4s.example.UnwrappedFancyList.Type type Username = smithy4s.example.Username.Type type UuidTrait = smithy4s.example.UuidTrait.Type + type ValidatedConstrainedIndexedSeqConstrainedMember = smithy4s.example.ValidatedConstrainedIndexedSeqConstrainedMember.Type + type ValidatedConstrainedList = smithy4s.example.ValidatedConstrainedList.Type + type ValidatedConstrainedListRefinedMember = smithy4s.example.ValidatedConstrainedListRefinedMember.Type + type ValidatedConstrainedMap = smithy4s.example.ValidatedConstrainedMap.Type + type ValidatedConstrainedVectorRefinedConstrainedMember = smithy4s.example.ValidatedConstrainedVectorRefinedConstrainedMember.Type + type ValidatedMapConstrainedKey = smithy4s.example.ValidatedMapConstrainedKey.Type + type ValidatedMapConstrainedValue = smithy4s.example.ValidatedMapConstrainedValue.Type + type ValidatedRefinedList = smithy4s.example.ValidatedRefinedList.Type + type ValidatedRefinedListConstrainedMember = smithy4s.example.ValidatedRefinedListConstrainedMember.Type + type ValidatedSetConstrainedMember = smithy4s.example.ValidatedSetConstrainedMember.Type type ValidatedString = smithy4s.example.ValidatedString.Type } \ No newline at end of file diff --git a/modules/bootstrapped/src/main/smithy4s/example/instances.scala b/modules/bootstrapped/src/main/smithy4s/example/instances.scala new file mode 100644 index 0000000000..a410205480 --- /dev/null +++ b/modules/bootstrapped/src/main/smithy4s/example/instances.scala @@ -0,0 +1,10 @@ +package smithy4s.example + +import smithy4s.RefinementProvider + +object instances { + + implicit val lengthProviderName + : RefinementProvider.Simple[smithy.api.Length, Name] = + RefinementProvider.lengthConstraint(_.value.value.length) +} diff --git a/modules/bootstrapped/test/src/smithy4s/ValidatedNewtypesSpec.scala b/modules/bootstrapped/test/src/smithy4s/ValidatedNewtypesSpec.scala index 1d65d0dae9..0af32286a7 100644 --- a/modules/bootstrapped/test/src/smithy4s/ValidatedNewtypesSpec.scala +++ b/modules/bootstrapped/test/src/smithy4s/ValidatedNewtypesSpec.scala @@ -16,7 +16,19 @@ package smithy4s -import smithy4s.schema.Schema.string +import smithy4s.example.Name +import smithy4s.example.ValidatedConstrainedList +import smithy4s.example.ValidatedSetConstrainedMember +import smithy4s.example.ValidatedConstrainedIndexedSeqConstrainedMember +import smithy4s.example.ValidatedConstrainedListRefinedMember +import smithy4s.example.ValidatedConstrainedVectorRefinedConstrainedMember +import smithy4s.example.ValidatedMapConstrainedKey +import smithy4s.example.ValidatedConstrainedMap +import smithy4s.example.ValidatedMapConstrainedValue +import smithy4s.example.ValidatedRefinedListConstrainedMember +import smithy4s.example.AccountId +import smithy4s.example.DeviceId + import munit.Assertions class ValidatedNewtypesSpec() extends munit.FunSuite { @@ -80,56 +92,126 @@ class ValidatedNewtypesSpec() extends munit.FunSuite { expect.same(accountIdCodec.decode(id1), Right(AccountId.unsafeApply(id1))) } - type DeviceId = DeviceId.Type - object DeviceId extends ValidatedNewtype[String] { - - val id: ShapeId = ShapeId("foo", "DeviceId") - val hints: Hints = Hints.empty + test("Validated constrained list") { + expect(ValidatedConstrainedList(List("foo")).isRight) + expect.same( + ValidatedConstrainedList(List("foo", "bar")), + Left("length required to be <= 1, but was 2") + ) + } - val underlyingSchema: Schema[String] = string - .withId(id) - .addHints(hints) - .validated(smithy.api.Length(min = Some(1L), max = None)) + test("Validated set constrained member") { + expect(ValidatedSetConstrainedMember(Set("f")).isRight) + expect.same( + ValidatedSetConstrainedMember(Set("foo")), + Left("length required to be <= 2, but was 3") + ) + } - val validator: Validator[String, DeviceId] = Validator - .of[String, DeviceId]( - Bijection[String, DeviceId](_.asInstanceOf[DeviceId], value(_)) - ) - .validating(smithy.api.Length(min = Some(1L), max = None)) + test("Validated constrained indexed seq constrained member") { + expect( + ValidatedConstrainedIndexedSeqConstrainedMember(IndexedSeq("f")).isRight + ) + expect.same( + ValidatedConstrainedIndexedSeqConstrainedMember(IndexedSeq("foo")), + Left("length required to be <= 2, but was 3") + ) + expect.same( + ValidatedConstrainedIndexedSeqConstrainedMember(IndexedSeq("f", "g")), + Left("length required to be <= 1, but was 2") + ) + expect.same( + ValidatedConstrainedIndexedSeqConstrainedMember(IndexedSeq("foo", "h")), + Left("length required to be <= 1, but was 2") + ) + } - implicit val schema: Schema[DeviceId] = - validator.toSchema(underlyingSchema) + test("Validated constrained list refined member") { + expect( + ValidatedConstrainedListRefinedMember( + List(Name(mkName("foo"))) + ).isRight + ) + expect.same( + ValidatedConstrainedListRefinedMember( + List( + Name(mkName("foo")), + Name(mkName("bar")) + ) + ), + Left("length required to be <= 1, but was 2") + ) + } - @inline def apply(a: String): Either[String, DeviceId] = - validator.validate(a) + test("Validated constrained vector refined & constrained member") { + expect( + ValidatedConstrainedVectorRefinedConstrainedMember( + Vector(Name(mkName("fo"))) + ).isRight + ) + expect.same( + ValidatedConstrainedVectorRefinedConstrainedMember( + Vector( + Name(mkName("fo")), + Name(mkName("ba")) + ) + ), + Left("length required to be <= 1, but was 2") + ) + expect.same( + ValidatedConstrainedVectorRefinedConstrainedMember( + Vector( + Name(mkName("foo")) + ) + ), + Left("length required to be <= 2, but was 3") + ) } - type AccountId = AccountId.Type - - object AccountId extends ValidatedNewtype[String] { - def id: smithy4s.ShapeId = ShapeId("foo", "AccountId") - val hints: Hints = Hints.empty + test("Validated refined list constrainer member") { + expect(ValidatedRefinedListConstrainedMember(mkNel("fo")).isRight) + expect.same( + ValidatedRefinedListConstrainedMember(mkNel("fo", "foo")), + Left("length required to be <= 2, but was 3") + ) + } - val underlyingSchema: Schema[String] = string - .withId(id) - .addHints(hints) - .validated(smithy.api.Length(min = Some(1L), max = None)) - .validated(smithy.api.Pattern("[a-zA-Z0-9]+")) + test("Validated constrained map") { + expect(ValidatedConstrainedMap(Map("foo" -> 1)).isRight) + expect.same( + ValidatedConstrainedMap(Map("foo" -> 1, "bar" -> 2)), + Left("length required to be <= 1, but was 2") + ) + } - val validator: Validator[String, AccountId] = Validator - .of[String, AccountId]( - Bijection[String, AccountId](_.asInstanceOf[AccountId], value(_)) - ) - .validating(smithy.api.Length(min = Some(1L), max = None)) - .alsoValidating(smithy.api.Pattern("[a-zA-Z0-9]+")) + test("Validated map constrained key") { + expect(ValidatedMapConstrainedKey(Map("a" -> 1, "b" -> 2)).isRight) + expect.same( + ValidatedMapConstrainedKey(Map("a" -> 1, "bar" -> 2)), + Left("length required to be <= 2, but was 3") + ) + } - implicit val schema: Schema[AccountId] = - validator.toSchema(underlyingSchema) + test("Validated map constrained value") { + expect( + ValidatedMapConstrainedValue(Map("a" -> "123", "b" -> "456")).isRight + ) + expect.same( + ValidatedMapConstrainedValue(Map("a" -> "123", "b" -> "4-5-6")), + Left("String '4-5-6' does not match pattern '^[a-zA-Z0-9]+$'") + ) + } - @inline def apply(a: String): Either[String, AccountId] = - validator.validate(a) + private def mkNel[A](elems: A*) = + smithy4s.refined.NonEmptyList(elems.toList) match { + case Left(msg) => fail(msg) + case Right(v) => v + } + private def mkName(str: String) = smithy4s.refined.Name(str) match { + case Left(msg) => fail(msg) + case Right(v) => v } } diff --git a/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala b/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala index 5bf4960603..49038ff013 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala @@ -1548,15 +1548,34 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self => hints: List[Hint] ): Lines = { val validator = { - val tags = hints.collect { case t: Hint.Constraint => t } - tags match { - case h :: tail => - ( - line".validating(${renderHint(h.native)})" +: - tail.map { tag => line".alsoValidating(${renderHint(tag.native)})" } - ).intercalate(Line.empty) - case _ => Line.empty + + def validateConstraints(h: List[Hint], validateMethod: Line) = h + .collect { case t: Hint.Constraint => t } + .foldMap(constraint => line".$validateMethod(${renderHint(constraint.native)})") + def validateType(t: Type): (Line, Line) = t match { + case Type.Collection(collectionTpe, memberTpe, memberHints) => + val builderMethod = collectionTpe match { + case CollectionType.List => line"list" + case CollectionType.Vector => line"vector" + case CollectionType.IndexedSeq => line"indexedSeq" + case CollectionType.Set => line"set" + } + line"$builderMethod[$memberTpe]" -> validateConstraints(memberHints, line"validatingMember") + case Type.Map(keyTpe, keyHints, valueTpe, valueHints) => + val validatingKey = validateConstraints(keyHints, line"validatingKey") + val validatingValue = validateConstraints(valueHints, line"validatingValue") + line"map[$keyTpe, $valueTpe]" -> (validatingKey + validatingValue) + case external: Type.ExternalType => + validateType(external.underlyingTpe) + case _ => line"simple[$tpe]" -> Line.empty } + val (builderType, validatingMembers) = validateType(tpe) + val validatingDirect = validateConstraints(hints, line"validating") + lines( + line"val validator: $Validator_[$tpe, $name] = $Validator_.Builder.$builderType$validatingDirect$validatingMembers${renderRefined( + tpe + )}.biject($Bijection_[$tpe, $name](_.asInstanceOf[$name], value(_))).build()" + ) } val definition = @@ -1568,13 +1587,12 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self => lines( documentationAnnotation(hints), deprecationAnnotation(hints), + renderScalaImports(hints), obj(name, line"$ValidatedNewtype_[$tpe]")( renderId(shapeId), renderHintsVal(hints), line"val underlyingSchema: $Schema_[$tpe] = ${tpe.schemaRef}$trailingCalls", - lines( - line"val validator: $Validator_[$tpe, $name] = $Validator_.of[$tpe, $name]($Bijection_[$tpe, $name](_.asInstanceOf[$name], value(_)))$validator" - ), + validator, lines( line"implicit val schema: $Schema_[$name] = ${definition}validator.toSchema(underlyingSchema)$closing" ), @@ -1645,18 +1663,8 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self => NameRef(ns, s"$name.schema").toLine case Type.Alias(ns, name, _, _) => NameRef(ns, s"$name.underlyingSchema").toLine - case Type.Ref(ns, name) => NameRef(ns, s"$name.schema").toLine - case e @ Type.ExternalType( - _, - _, - _, - maybeProviderImport, - underlyingTpe, - hint - ) => - line"${underlyingTpe.schemaRef}.refined[${e: Type}](${renderHint(hint)})${maybeProviderImport - .map { providerImport => Import(providerImport).toLine } - .getOrElse(Line.empty)}" + case Type.Ref(ns, name) => NameRef(ns, s"$name.schema").toLine + case e: Type.ExternalType => line"${e.underlyingTpe.schemaRef}${renderRefined(e)}" case Nullable(underlying) => line"${underlying.schemaRef}.option" } @@ -1706,6 +1714,21 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self => .run(true) ._2 + private def renderRefined(tpe: Type): Line = tpe match { + case e @ Type.ExternalType( + _, + _, + _, + maybeProviderImport, + _, + hint + ) => + line".refined[${e: Type}](${renderHint(hint)})${maybeProviderImport + .map { providerImport => Import(providerImport).toLine } + .getOrElse(Line.empty)}" + case _ => Line.empty + } + def renderId(shapeId: ShapeId): Line = { val ns = shapeId.getNamespace() val name = shapeId.getName() diff --git a/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala b/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala index 38ae23bc7d..d3d0142f42 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala @@ -646,7 +646,7 @@ private[codegen] class SmithyToIR( shape.hasTrait(classOf[smithy4s.meta.UnwrapTrait]) } - def primitive( + private def primitive( shape: Shape, primitiveId: String, primitive: Primitive @@ -706,7 +706,7 @@ private[codegen] class SmithyToIR( } } - def listShape(x: ListShape): Option[Type] = { + def listShape(x: ListShape): Option[Type] = x.getMember() .accept(this) .map { tpe => @@ -728,12 +728,16 @@ private[codegen] class SmithyToIR( } } .map { tpe => - val externalOrBase = - getExternalOrBase(x, tpe) - val isUnwrapped = !isExternal(externalOrBase) || isUnwrappedShape(x) - Type.Alias(x.namespace, x.name, externalOrBase, isUnwrapped) + val externalOrBase = getExternalOrBase(x, tpe) + val shouldValidate = x.hasTrait(classOf[ValidateNewtypeTrait]) + if (shouldValidate) + Type.ValidatedAlias(x.namespace, x.name, externalOrBase) + else { + val isUnwrapped = + !isExternal(externalOrBase) || isUnwrappedShape(x) + Type.Alias(x.namespace, x.name, externalOrBase, isUnwrapped) + } } - } @nowarn("msg=class SetShape in package shapes is deprecated") override def setShape(x: SetShape): Option[Type] = { @@ -769,8 +773,13 @@ private[codegen] class SmithyToIR( )).map { tpe => val externalOrBase = getExternalOrBase(x, tpe) - val isUnwrapped = !isExternal(externalOrBase) || isUnwrappedShape(x) - Type.Alias(x.namespace, x.name, externalOrBase, isUnwrapped) + val shouldValidate = x.hasTrait(classOf[ValidateNewtypeTrait]) + if (shouldValidate) + Type.ValidatedAlias(x.namespace, x.name, externalOrBase) + else { + val isUnwrapped = !isExternal(externalOrBase) || isUnwrappedShape(x) + Type.Alias(x.namespace, x.name, externalOrBase, isUnwrapped) + } } def byteShape(x: ByteShape): Option[Type] = @@ -846,9 +855,7 @@ private[codegen] class SmithyToIR( builder .addTraits(x.getAllTraits().asScala.map(_._2).asJavaCollection) - builder - .build() - .accept(this) + builder.build().accept(this) } def timestampShape(x: TimestampShape): Option[Type] = diff --git a/modules/core/src/smithy4s/Validator.scala b/modules/core/src/smithy4s/Validator.scala index 822e60dc0c..f93ae42154 100644 --- a/modules/core/src/smithy4s/Validator.scala +++ b/modules/core/src/smithy4s/Validator.scala @@ -16,55 +16,382 @@ package smithy4s -sealed trait Validator[A, B] { +sealed trait Validator[A, B] { self => def validate(value: A): Either[String, B] + // todo: deprecate def toSchema(a: Schema[A]): Schema[B] def alsoValidating[C](constraint: C)(implicit ev: RefinementProvider.Simple[C, A] - ): Validator[A, B] + ): Validator[A, B] = toBuilder.validating(constraint).build() + + def toBuilder: Validator.Builder[A, B] + } object Validator { - def of[A, B](bijection: Bijection[A, B]): ValidatorBuilder[A, B] = - new ValidatorBuilder[A, B](bijection) - - final class ValidatorBuilder[A, B] private[smithy4s] ( - bijection: Bijection[A, B] - ) { + private[smithy4s] trait Builder[A, B] { self => def validating[C](constraint: C)(implicit ev: RefinementProvider.Simple[C, A] - ): Validator[A, B] = - new ValidatorImpl[A, B](List(ev.make(constraint)), bijection) + ): Builder[A, B] + + def biject[B0](implicit bijection: Bijection[B, B0]): Builder[A, B0] = + new Builder.Bijected(self, bijection) + + def build(): Validator[A, B] } - private class ValidatorImpl[A, B]( - refinements: List[Refinement.Aux[_, A, A]], - bijection: Bijection[A, B] - ) extends Validator[A, B] { + private[smithy4s] final class RefinedSyntax[A](builder: Builder[A, A]) { + def refined[C]: Builder.PartiallyAppliedRefinedBuilder[A, C] = + new Builder.PartiallyAppliedRefinedBuilder[A, C](builder) + } + + object Builder { + + implicit def toRefinedSyntax[A, B](builder: Builder[A, B])(implicit + ev: A =:= B + ): RefinedSyntax[A] = { + val _ = ev + new RefinedSyntax(builder.asInstanceOf[Builder[A, A]]) + } + + private[smithy4s] class PartiallyAppliedRefinedBuilder[A, B]( + base: Builder[A, A] + ) { + def apply[C](c: C)(implicit + ev: RefinementProvider[C, A, B] + ): Builder[B, B] = new Builder.Refined(base, ev.make(c), Vector.empty) + } + + private[smithy4s] trait Collection[Col[_], E] + extends Builder[Col[E], Col[E]] { + override def validating[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, Col[E]] + ): Builder.Collection[Col, E] + + def validatingMember[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, E] + ): Collection[Col, E] + + } + + private[smithy4s] trait KeyValue[K, V] + extends Builder[Map[K, V], Map[K, V]] { + override def validating[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, Map[K, V]] + ): KeyValue[K, V] + + def validatingKey[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, K] + ): KeyValue[K, V] + + def validatingValue[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, V] + ): KeyValue[K, V] + + } + + def simple[A]: Builder[A, A] = new Simple(Vector.empty) + + def list[E]: Builder.Collection[List, E] = + collection(schema.CollectionTag.ListTag) + + def set[E]: Builder.Collection[Set, E] = + collection(schema.CollectionTag.SetTag) + + def vector[E]: Builder.Collection[Vector, E] = + collection(schema.CollectionTag.VectorTag) + + def indexedSeq[E]: Builder.Collection[IndexedSeq, E] = + collection(schema.CollectionTag.IndexedSeqTag) + + def map[K, V]: Builder.KeyValue[K, V] = + new KeyValueImpl[K, V](None, Vector.empty, Vector.empty) + + private def collection[C[_], E]( + tag: schema.CollectionTag[C] + ): Builder.Collection[C, E] = collection(tag.iterator(_)) + + private def collection[Col[_], E]( + getIterator: Col[E] => Iterator[E] + ): Builder.Collection[Col, E] = + new CollectionImpl(getIterator, None, Vector.empty) + + private final class Simple[A]( + refinements: Vector[Refinement[A, A]] + ) extends Builder[A, A] { self => + + override def validating[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, A] + ): Builder[A, A] = + new Simple[A](refinements :+ ev.make(constraint)) + + override def build(): Validator[A, A] = new Validator[A, A] { + override def validate(value: A): Either[String, A] = + refinements + .foldLeft(Right(value): Either[String, A]) { + case (valueOrError, refinement) => + valueOrError.flatMap(refinement.apply) + } + + override def toSchema(a: Schema[A]): Schema[A] = + refinements + .foldLeft(a) { (schema, refinement) => + schema.refined[A](refinement) + } + + override def toBuilder: Builder[A, A] = self + + } + + } + + private final class Bijected[A, B, B0]( + source: Builder[A, B], + bijectTarget: Bijection[B, B0] + ) extends Builder[A, B0] { self => + + override def biject[B1](implicit + bijection: Bijection[B0, B1] + ): Builder[A, B1] = + new Bijected( + source, + bijectTarget.imapTarget(bijection) + ) + + override def validating[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, A] + ): Builder[A, B0] = + new Bijected( + source.validating(constraint), + bijectTarget + ) + + override def build(): Validator[A, B0] = new Validator[A, B0] { + val sourceValidator = source.build() + + override def validate(value: A): Either[String, B0] = + sourceValidator.validate(value).map(bijectTarget.to) + + override def toSchema(a: Schema[A]): Schema[B0] = + sourceValidator.toSchema(a).biject(bijectTarget) + + override def toBuilder: Builder[A, B0] = self + } + } + + private final class Refined[A, B]( + mainBuilder: Builder[A, A], + refinement: Refinement[A, B], + additionalRefinements: Vector[Refinement[B, B]] + ) extends Builder[B, B] { self => + + private val mainValidator = mainBuilder.build() + override def validating[C]( + c: C + )(implicit ev: RefinementProvider.Simple[C, B]): Builder[B, B] = + new Refined( + mainBuilder, + refinement, + additionalRefinements = additionalRefinements :+ ev.make(c) + ) + + override def build(): Validator[B, B] = new Validator[B, B] { + override def validate(value: B): Either[String, B] = + mainValidator + .validate(refinement.from(value)) + .flatMap(_ => + additionalRefinements.foldLeft(right(value)) { case (acc, ref) => + acc.flatMap(_ => ref.apply(value)) + } + ) + override def toSchema(a: Schema[B]): Schema[B] = a + + override def toBuilder: Builder[B, B] = self + + } + + } + + private final class KeyValueImpl[K, V]( + mainBuilder: Option[Builder[Map[K, V], Map[K, V]]], + keyRefinements: Vector[Refinement[K, K]], + valueRefinements: Vector[Refinement[V, V]] + ) extends Builder.KeyValue[K, V] { self => + + override def validating[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, Map[K, V]] + ): KeyValue[K, V] = + new KeyValueImpl( + mainBuilder = mainBuilder + .map(_.validating(constraint)) + .orElse(Some(simple.validating(constraint))), + keyRefinements = keyRefinements, + valueRefinements = valueRefinements + ) + + override def validatingKey[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, K] + ): KeyValue[K, V] = new KeyValueImpl( + mainBuilder = mainBuilder, + keyRefinements = keyRefinements :+ ev.make(constraint), + valueRefinements = valueRefinements + ) + + override def validatingValue[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, V] + ): KeyValue[K, V] = new KeyValueImpl( + mainBuilder = mainBuilder, + keyRefinements = keyRefinements, + valueRefinements = valueRefinements :+ ev.make(constraint) + ) + + override def build(): Validator[Map[K, V], Map[K, V]] = + new Validator[Map[K, V], Map[K, V]] { + val mainValidator = mainBuilder.map(_.build()) + override def validate( + value: Map[K, V] + ): Either[String, Map[K, V]] = { + val mainValidated = + mainValidator.map(_.validate(value)).getOrElse(right(())) + def validateKeySet = validateCollection( + value.iterator + ) { case (k, v) => + validateRefinements(keyRefinements)(k).flatMap(_ => + validateRefinements(valueRefinements)(v) + ) + } + mainValidated.flatMap(_ => validateKeySet).map(_ => value) + } + + override def toSchema(a: Schema[Map[K, V]]): Schema[Map[K, V]] = { + val main = mainValidator.map(_.toSchema(a)).getOrElse(a) + main match { + case mapSchema @ Schema.MapSchema(_, _, key, value) => + val newKey = keyRefinements.foldLeft(key) { + case (acc, refinement) => acc.refined(refinement) + } + val newValue = valueRefinements.foldLeft(value) { + case (acc, refinement) => acc.refined(refinement) + } + mapSchema.copy(key = newKey, value = newValue) + case _ => main + } + } + + override def toBuilder: Builder[Map[K, V], Map[K, V]] = self - override def validate(value: A): Either[String, B] = { - refinements - .foldLeft(Right(value): Either[String, A]) { - case (valueOrError, refinement) => - valueOrError.flatMap(refinement.apply) } - .map(bijection.apply) + } - override def alsoValidating[C](constraint: C)(implicit - ev: RefinementProvider.Simple[C, A] - ): Validator[A, B] = - new ValidatorImpl[A, B](refinements :+ ev.make(constraint), bijection) + private final class CollectionImpl[Col[_], Elem]( + getIterator: Col[Elem] => Iterator[Elem], + mainBuilder: Option[Builder[Col[Elem], Col[Elem]]], + memberRefinements: Vector[Refinement[Elem, Elem]] + ) extends Builder.Collection[Col, Elem] { self => + + override def validating[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, Col[Elem]] + ): Collection[Col, Elem] = + new CollectionImpl( + getIterator, + mainBuilder = mainBuilder + .map(_.validating(constraint)) + .orElse(Some(simple.validating(constraint))), + memberRefinements = memberRefinements + ) - override def toSchema(a: Schema[A]): Schema[B] = { - refinements - .foldLeft(a) { (schema, refinement) => - schema.refined[A](refinement) + override def validatingMember[C]( + constraint: C + )(implicit + ev: RefinementProvider.Simple[C, Elem] + ): Collection[Col, Elem] = + new CollectionImpl( + getIterator, + mainBuilder, + memberRefinements :+ ev.make(constraint) + ) + + override def build(): Validator[Col[Elem], Col[Elem]] = + new Validator[Col[Elem], Col[Elem]] { + val mainValidator = mainBuilder.map(_.build()) + override def validate( + value: Col[Elem] + ): Either[String, Col[Elem]] = { + val elementsValidated = validateCollection(getIterator(value)) { + validateRefinements(memberRefinements) + } + val mainValidated = + mainValidator + .map(_.validate(value).map(_ => ())) + .getOrElse( + right(()) + ) + mainValidated.flatMap(_ => elementsValidated.map(_ => value)) + } + + override def toSchema(a: Schema[Col[Elem]]): Schema[Col[Elem]] = { + val main = mainValidator.map(_.toSchema(a)).getOrElse(a) + main match { + case collectionSchema @ Schema.CollectionSchema( + _, + _, + _, + member + ) => + val newMember = memberRefinements.foldLeft(member) { + case (acc, refinement) => acc.refined(refinement) + } + collectionSchema.copy(member = newMember) + case _ => main + } + } + + override def toBuilder: Builder[Col[Elem], Col[Elem]] = self } - .biject(bijection) } + + private def right[A](value: A): Either[String, A] = Right(value) + + private def validateRefinements[A]( + refinements: Vector[Refinement[A, A]] + )(elem: A): Either[String, Unit] = refinements + .foldLeft(right(elem)) { case (result, constraint) => + result.flatMap(_ => constraint(elem)) + } + .map(_ => ()) + + private def validateCollection[A]( + it: Iterator[A] + )(validate: A => Either[String, Unit]): Either[String, Unit] = { + var acc: Either[String, Unit] = Right(()) + while (it.hasNext && acc.isRight) { + acc = validate(it.next()) + } + acc + } + + } + + @deprecated + def of[A, B](bijection: Bijection[A, B]): ValidatorBuilder[A, B] = + new ValidatorBuilder[A, B](bijection) + + @deprecated + final class ValidatorBuilder[A, B] private[smithy4s] ( + bijection: Bijection[A, B] + ) { + def validating[C](constraint: C)(implicit + ev: RefinementProvider.Simple[C, A] + ): Validator[A, B] = + Validator.Builder + .simple[A] + .validating(constraint) + .biject(bijection) + .build() } + } diff --git a/modules/docs/resources/markdown/04-codegen/01-customisation/15-validated-newtypes.md b/modules/docs/resources/markdown/04-codegen/01-customisation/15-validated-newtypes.md index c049eee12a..8013764cc3 100644 --- a/modules/docs/resources/markdown/04-codegen/01-customisation/15-validated-newtypes.md +++ b/modules/docs/resources/markdown/04-codegen/01-customisation/15-validated-newtypes.md @@ -37,6 +37,7 @@ import smithy4s.schema.Schema.string type Name = Name.Type object Name extends ValidatedNewtype[String] { + val id: ShapeId = ShapeId("smithy4s.example", "Name") val hints: Hints = Hints.empty @@ -46,13 +47,15 @@ object Name extends ValidatedNewtype[String] { .withId(id) .addHints(hints) .validated(smithy.api.Length(min = Some(5L), max = None)) - + val validator: Validator[String, Name] = - Validator.of[String, Name](Bijection[String, Name](_.asInstanceOf[Name], value(_))) + Validator.Builder.simple[String] .validating(smithy.api.Length(min = Some(5L), max = None)) + .biject(Bijection[String, Name](_.asInstanceOf[Name], value(_))).build() implicit val schema: Schema[Name] = validator.toSchema(underlyingSchema) @inline def apply(a: String): Either[String, Name] = validator.validate(a) } + ``` diff --git a/modules/protocol/resources/META-INF/smithy/smithy4s.meta.smithy b/modules/protocol/resources/META-INF/smithy/smithy4s.meta.smithy index 7d02d8987b..de86e48ab2 100644 --- a/modules/protocol/resources/META-INF/smithy/smithy4s.meta.smithy +++ b/modules/protocol/resources/META-INF/smithy/smithy4s.meta.smithy @@ -196,7 +196,12 @@ list scalaImports { :is( number[trait|range], string[trait|pattern], - string[trait|length] + string[trait|length], + :test(-[trait]-> [trait|smithy4s.meta#refinement]), + list[trait|length], + map[trait|length], + list:test(> [trait|length], > [trait|range], > [trait|pattern]), + map:test(> [trait|length], > [trait|range], > [trait|pattern]) )""" conflicts: [unwrap] ) diff --git a/sampleSpecs/validated-newtype.smithy b/sampleSpecs/validated-newtype.smithy index 8819b906f2..03ab57a0aa 100644 --- a/sampleSpecs/validated-newtype.smithy +++ b/sampleSpecs/validated-newtype.smithy @@ -3,12 +3,21 @@ $version: "2" namespace smithy4s.example use smithy4s.meta#validateNewtype +use smithy4s.meta#scalaImports @length(min: 1) @pattern("[a-zA-Z0-9]+") @validateNewtype string ValidatedString +@pattern("[a-zA-Z0-9]+") +@validateNewtype +string AccountId + +@validateNewtype +@length(min: 1) +string DeviceId + @length(min: 1) @pattern("[a-zA-Z0-9]+") string NonValidatedString @@ -16,3 +25,80 @@ string NonValidatedString structure ValidatedFoo { name: ValidatedString = "abc" } + +@length(max: 1) +@validateNewtype +list ValidatedConstrainedList { + member: String +} + +@validateNewtype +@uniqueItems +list ValidatedSetConstrainedMember { + @length(max: 2) + member: String +} + +@validateNewtype +@length(max: 1) +@smithy4s.meta#indexedSeq +list ValidatedConstrainedIndexedSeqConstrainedMember { + @length(max: 2) + member: String +} + +@validateNewtype +@length(max: 1) +list ValidatedConstrainedListRefinedMember { + member: Name +} + +@validateNewtype +@length(max: 1) +@scalaImports(["smithy4s.example.instances._"]) +@smithy4s.meta#vector +list ValidatedConstrainedVectorRefinedConstrainedMember { + @length(max:2) + member: Name +} + +@nonEmptyListFormat +@validateNewtype +list ValidatedRefinedList { + member: String +} + +@nonEmptyListFormat +@validateNewtype +list ValidatedRefinedListConstrainedMember { + @length(max: 2) + member: String +} + +@length(max: 1) +@validateNewtype +map ValidatedConstrainedMap { + key: String + value: Integer +} + +@validateNewtype +map ValidatedMapConstrainedKey { + @length(max: 2) + key: String + value: Integer +} + +@validateNewtype +map ValidatedMapConstrainedValue { + key: String + @pattern("^[a-zA-Z0-9]+$") + value: String +} + +// @nonEmptyMapFormat +// @validateNewtype +// map ValidatedRefinedMap { +// key: String +// value: Integer +// } \ No newline at end of file