diff --git a/.gitignore b/.gitignore index 940794e..f9cb0ce 100644 --- a/.gitignore +++ b/.gitignore @@ -262,6 +262,7 @@ paket-files/ # FAKE - F# Make .fake/ +.ionide/ # JetBrains Rider .idea/ diff --git a/FunctionalCuid.Tests/Program.fs b/FunctionalCuid.Tests/Program.fs index 95cdb78..be67f5e 100644 --- a/FunctionalCuid.Tests/Program.fs +++ b/FunctionalCuid.Tests/Program.fs @@ -15,29 +15,36 @@ module CuidTests = ] [] - let fromStringTests = - testList "Cuid.fromString" [ + let ofStringTests = + testList "Cuid.ofString" [ test "fails when string is null" { - let x = Cuid.fromString null + let x = Cuid.ofString null Expect.isError x "Parsing should have returned an error" let msg = match x with Error y -> y | _ -> "" Expect.equal msg "string was null" "Expected error message not returned" } test "fails when string is not 25 characters" { - let x = Cuid.fromString "c12345566677893508" + let x = Cuid.ofString "c12345566677893508" Expect.isError x "Parsing should have returned an error" let msg = match x with Error y -> y | _ -> "" Expect.equal msg "string was not 25 characters (length 18)" "Expected error message not returned" } test "fails when string does not start with c" { - let x = Cuid.fromString "djld2cyuq0000t3rmniod1foy" + let x = Cuid.ofString "djld2cyuq0000t3rmniod1foy" Expect.isError x "Parsing should have returned an error" let msg = match x with Error y -> y | _ -> "" Expect.equal msg """string did not start with "c" ("djld2cyuq0000t3rmniod1foy")""" "Expected error message not returned" } + test "fails when string is not valid base-36" { + let x = Cuid.ofString "cjld2*yuq0/00t3r#niod1foy" + Expect.isError x "Parsing should have returned an error" + let msg = match x with Error y -> y | _ -> "" + Expect.equal msg """string was not in base-36 format ("cjld2*yuq0/00t3r#niod1foy")""" + "Expected error message not returned" + } test "succeeds" { - let x = Cuid.fromString "cjld2cyuq0000t3rmniod1foy" + let x = Cuid.ofString "cjld2cyuq0000t3rmniod1foy" Expect.isOk x "Parsing should have returned Ok" let cuid = match x with Ok y -> y | _ -> Cuid "" Expect.equal cuid (Cuid "cjld2cyuq0000t3rmniod1foy") "CUID value not correct" @@ -53,6 +60,30 @@ module CuidTests = } ] + [] + let isValidTests = + testList "Cuid.isValid" [ + test "succeeds when the string is a valid CUID" { + Expect.isTrue ((Cuid.generateString >> Cuid.isValid) ()) "A valid CUID should have returned true" + } + test "succeeds when the string is not a valid CUID" { + Expect.isFalse (Cuid.isValid "abc") "An invalid CUID should have returned false" + } + ] + + [] + let validationMessageTests = + testList "Cuid.validationMessage" [ + test "succeeds when the string is a valid CUID" { + Expect.equal ((Cuid.generateString >> Cuid.validationMessage) ()) "" + "A valid CUID should have returned an empty validation message" + } + test "succeeds when the string is an invalid CUID" { + Expect.equal (Cuid.validationMessage null) "string was null" + "An invalid CUID should have returned its validation error message" + } + ] + [] let generateStringTests = testList "Cuid.generateString" [ @@ -77,28 +108,34 @@ module SlugTests = ] [] - let fromStringTests = - testList "Slug.fromString" [ + let ofStringTests = + testList "Slug.ofString" [ test "fails when string is null" { - let x = Slug.fromString null + let x = Slug.ofString null Expect.isError x "Parsing should have returned an error" let msg = match x with Error y -> y | _ -> "" Expect.equal msg "string was null" "Expected error message not returned" } test "fails when string is less than 7 characters" { - let x = Slug.fromString "12345" + let x = Slug.ofString "12345" Expect.isError x "Parsing should have returned an error" let msg = match x with Error y -> y | _ -> "" Expect.equal msg "string must be at least 7 characters (length 5)" "Expected error message not returned" } test "fails when string is more than 10 characters" { - let x = Slug.fromString "abcdefghijklmnop" + let x = Slug.ofString "abcdefghijklmnop" Expect.isError x "Parsing should have returned an error" let msg = match x with Error y -> y | _ -> "" Expect.equal msg "string must not exceed 10 characters (length 16)" "Expected error message not returned" } + test "fails when string is not valid base-36" { + let x = Slug.ofString "cj*q0/0#d" + Expect.isError x "Parsing should have returned an error" + let msg = match x with Error y -> y | _ -> "" + Expect.equal msg """string was not in base-36 format ("cj*q0/0#d")""" "Expected error message not returned" + } test "succeeds" { - let x = Slug.fromString "cyuq0000t" + let x = Slug.ofString "cyuq0000t" Expect.isOk x "Parsing should have returned Ok" let slug = match x with Ok y -> y | _ -> Slug "" Expect.equal slug (Slug "cyuq0000t") "Slug value not correct" @@ -114,6 +151,30 @@ module SlugTests = } ] + [] + let isValidTests = + testList "Slug.isValid" [ + test "succeeds when the string is a valid Slug" { + Expect.isTrue ((Slug.generateString >> Slug.isValid) ()) "A valid Slug should have returned true" + } + test "succeeds when the string is not a valid Slug" { + Expect.isFalse (Slug.isValid "12345") "An invalid Slug should have returned false" + } + ] + + [] + let validationMessageTests = + testList "Slug.validationMessage" [ + test "succeeds when the string is a valid Slug" { + Expect.equal ((Slug.generateString >> Slug.validationMessage) ()) "" + "A valid Slug should have returned an empty validation message" + } + test "succeeds when the string is an invalid Slug" { + Expect.equal (Slug.validationMessage null) "string was null" + "An invalid Slug should have returned its validation error message" + } + ] + [] let generateStringTests = testList "Slug.generateString" [ diff --git a/FunctionalCuid/FunctionalCuid.fsproj b/FunctionalCuid/FunctionalCuid.fsproj index 2635dd1..2503630 100644 --- a/FunctionalCuid/FunctionalCuid.fsproj +++ b/FunctionalCuid/FunctionalCuid.fsproj @@ -2,6 +2,17 @@ netstandard2.0 + 1.0.0 + Initial release + danieljsummers + Bit Badger Solutions + https://github.com/danieljsummers/FunctionalCuid + false + https://github.com/danieljsummers/FunctionalCuid + Git + MIT License + MIT + Cuid Slug F-Sharp diff --git a/FunctionalCuid/Library.fs b/FunctionalCuid/Library.fs index ba325fe..b4dadb2 100644 --- a/FunctionalCuid/Library.fs +++ b/FunctionalCuid/Library.fs @@ -102,6 +102,12 @@ module private Support = match chars with | _ when chars > str.Length -> str | _ -> str.[str.Length - chars..] + + /// Convert a result to a boolean + let resultToBool x = match x with Ok _ -> true | Error _ -> false + + /// Get the error message for a result with a string error message + let errMsg x = match x with Ok _ -> "" | Error msg -> msg /// Public functions for the CUID type @@ -135,8 +141,7 @@ module Cuid = /// - be 25 characters long /// - start with "c" /// - be base-36 format ([0-9|a-z]) - // TODO: extract these validations out so we can provide a "validate" function for C#/VB.NET - let fromString (x : string) = + let ofString (x : string) = match x with | null -> Error "string was null" | _ when x.Length <> 25 -> (sprintf "string was not 25 characters (length %i)" >> Error) x.Length @@ -147,6 +152,14 @@ module Cuid = /// Get the string representation of a CUID let toString x = match x with Cuid y -> y + /// Is the string a valid CUID? + [] + let isValid = ofString >> resultToBool + + /// Get the validation message for a CUID string + [] + let validationMessage = ofString >> errMsg + /// Generate a CUID as a string [] let generateString = generate >> toString @@ -177,8 +190,7 @@ module Slug = /// Create a Slug from a string /// /// The string must be between 7 and 10 characters long and base-36 format ([0-9|a-z]) - // TODO: extract these validations out so we can provide a "validate" function for C#/VB.NET - let fromString (x : string) = + let ofString (x : string) = match x with | null -> Error "string was null" | _ when x.Length < 7 -> (sprintf "string must be at least 7 characters (length %i)" >> Error) x.Length @@ -189,6 +201,14 @@ module Slug = /// Get the string representation of a Slug let toString x = match x with Slug y -> y + /// Is the string a valid Slug? + [] + let isValid = ofString >> resultToBool + + /// Get the validation message for a Slug string + [] + let validationMessage = ofString >> errMsg + /// Generate a Slug as a string [] let generateString = generate >> toString diff --git a/README.md b/README.md index 11b6e0b..1c64a28 100644 --- a/README.md +++ b/README.md @@ -25,23 +25,45 @@ open Cuid let generatedCuid = Cuid.generate () /// Creating a CUID from a string you already know. This string must be 25 -/// characters long and start with "c". -let cuidFromString = - match Cuid.fromString "cjz362bgd00005cus6t21gba0" with +/// characters long, start with "c", and be valid base-36 (0-9 and a-z). +let cuidOfString = + match Cuid.ofString "cjz362bgd00005cus6t21gba0" with | Error msg -> invalidOp msg | Ok cuid -> cuid +/// Establish a valid CUID string using isValid and validationMessage instead. +let validatedCuidString = + let str = "abcdefg" + match Cuid.isValid str with + | true -> str + | false -> (Cuid.validationMessage >> invalidOp) str + +/// Get the validation error for a CUID (empty string if CUID is valid). +let cuidErrorMsg = Cuid.validationMessage "howdy" + /// The string representation of a CUID let cuidString = Cuid.generateString () ``` -For the `Slug` type, just replace `Cuid` with `Slug`; the validation rules for `Slug.fromString` are simply that the string has to be between 7 and 10 characters long. +For the `Slug` type, just replace `Cuid` with `Slug`; the validation rules for `Slug.ofString` are that the string has to be between 7 and 10 base-36 characters long. -For C# and VB.NET, the syntax is a bit different. Instead of `Cuid` as it reads above, it will appear as `CuidModule`; and, as `generateString` is the most likely function (method) called from those languages, its compiled name uses Pascal case. The same holds true for the `Slug` modules as well. A C# example... +For C# and VB.NET, the syntax is a bit different. Instead of `Cuid` as it reads above, it will appear as `CuidModule`; and, as `generateString`, `isValid`, and `validationMessage` are the most likely functions (methods) called from those languages, their compiled names use Pascal case. The same holds true for the `Slug` modules as well. A C# example... ```csharp using Cuid; // ... var cuid = CuidModule.GenerateString(); -var slug = SlugModule.GenerateString(); + +// example from an MVC controller +public IActionResult Get(string cuid) +{ + if (CuidModule.IsValid(cuid)) + { + // do something with it + } + else + { + return NotFound($"Could not find ID {cuid}; {CuidModule.ValidationMessage(cuid)}"); + } +} ```