diff --git a/Readme.md b/Readme.md index 97c093d..6ef9c9f 100644 --- a/Readme.md +++ b/Readme.md @@ -21,7 +21,7 @@ This library add support for the following types: Implementation based on this [UUID RFC Draft](https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-03) In addition to UUID, there is also support for [TypeIDs](https://github.com/jetpack-io/typeid). TypeIDs are a modern, -type-safe extension of UUIDv7 +type-safe extension of UUIDv7. This implementation is compatible with the [0.3.0 version of the specification](https://github.com/jetify-com/typeid/tree/main/spec#typeid-specification-version-030) ## Quickstart diff --git a/core/src/main/scala/tech/ant8e/uuid4cats/TypeID.scala b/core/src/main/scala/tech/ant8e/uuid4cats/TypeID.scala index 496922f..acf2ac9 100644 --- a/core/src/main/scala/tech/ant8e/uuid4cats/TypeID.scala +++ b/core/src/main/scala/tech/ant8e/uuid4cats/TypeID.scala @@ -110,8 +110,7 @@ object TypeID { enforceUUIDV7: Boolean = true ): ValidatedNec[DecodeError, TypeID] = { val re = - "^([a-z]{0,63})(_?)([0123456789abcdefghjkmnpqrstvwxyz]+)$".r - + "^([a-z](?:[a-z_]{0,61}[a-z])?)?(_)?([0123456789abcdefghjkmnpqrstvwxyz]{26})$".r def parseID(id: String): Validated[DecodeError, UUID] = UUIDBase32 .fromBase32(id) @@ -124,7 +123,7 @@ object TypeID { } typeIDString match { - case re(prefix, sep, id) => + case re(prefix, sep, id) if prefix != null && sep != null => ( Validated .cond( @@ -135,6 +134,8 @@ object TypeID { .toValidatedNec, parseID(id).toValidatedNec ).mapN { case (prefix_, uuid_) => newTypeID(prefix_, uuid_) } + case re(null, null, id) => + parseID(id).toValidatedNec.map(uuid => newTypeID("", uuid)) case _ => Validated.invalidNec(NotParseableTypeID) } @@ -207,7 +208,7 @@ object TypeID { prefix: String ): Validated[BuildError, String] = Option(prefix) .map(prefix_ => { - if ("[a-z]{0,63}".r.matches(prefix_)) + if ("^([a-z]([a-z_]{0,61}[a-z])?)?$".r.matches(prefix_)) Validated.Valid(prefix_) else Validated.Invalid(InvalidPrefix) }) diff --git a/core/src/test/scala/tech/ant8e/uuid4cats/TypeIDSuite.scala b/core/src/test/scala/tech/ant8e/uuid4cats/TypeIDSuite.scala index b690820..2b750d4 100644 --- a/core/src/test/scala/tech/ant8e/uuid4cats/TypeIDSuite.scala +++ b/core/src/test/scala/tech/ant8e/uuid4cats/TypeIDSuite.scala @@ -82,7 +82,15 @@ class TypeIDSuite extends FunSuite { prefix = "prefix", uuid = uuid"01890a5d-ac96-774b-bcce-b302099a8057" ) + + // prefix-underscore added in v0.3.0 + assertValidEncoding( + typeID = "pre_fix_00000000000000000000000000", + prefix = "pre_fix", + uuid = uuid"00000000-0000-0000-0000-000000000000" + ) } + test("TypeID should not build invalid typeIDs") { val tooLongPrefix = "0123456789012345678901234567890123456789012345678901234567890123456789" @@ -167,9 +175,6 @@ class TypeIDSuite extends FunSuite { // prefix-period assertInvalidDecoding(typeID = "pre.fix_00000000000000000000000000") - // prefix-underscore - assertInvalidDecoding(typeID = "pre_fix_00000000000000000000000000") - // prefix-non-ascii assertInvalidDecoding(typeID = "préfix_00000000000000000000000000") @@ -224,6 +229,17 @@ class TypeIDSuite extends FunSuite { assertInvalidDecoding( typeID = "prefix0000000000000000000000000g" ) + + // prefix-underscore-start + assertInvalidDecoding( + typeID = "_prefix_00000000000000000000000000" + ) + + // prefix-underscore-end + assertInvalidDecoding( + typeID = "prefix__00000000000000000000000000" + ) + } test("TypeID should have an Eq instance") {