diff --git a/gleam/secure-treasure-chest/.gitignore b/gleam/secure-treasure-chest/.gitignore new file mode 100644 index 0000000..170cca9 --- /dev/null +++ b/gleam/secure-treasure-chest/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +build +erl_crash.dump diff --git a/gleam/secure-treasure-chest/HELP.md b/gleam/secure-treasure-chest/HELP.md new file mode 100644 index 0000000..0a17d3e --- /dev/null +++ b/gleam/secure-treasure-chest/HELP.md @@ -0,0 +1,32 @@ +# Help + +## Running the tests + +To run the tests, run the command `gleam test` from within the exercise directory. + +## Submitting your solution + +You can submit your solution using the `exercism submit src/secure_treasure_chest.gleam` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Gleam track's documentation](https://exercism.org/docs/tracks/gleam) +- The [Gleam track's programming category on the forum](https://forum.exercism.org/c/programming/gleam) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +To get help if you're having trouble, you can use one of the following resources: + +- [gleam.run](https://gleam.run/documentation/) is the gleam official documentation. +- [Discord](https://discord.gg/Fm8Pwmy) is the discord channel. +- [StackOverflow](https://stackoverflow.com/questions/tagged/gleam) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. \ No newline at end of file diff --git a/gleam/secure-treasure-chest/HINTS.md b/gleam/secure-treasure-chest/HINTS.md new file mode 100644 index 0000000..d6451a8 --- /dev/null +++ b/gleam/secure-treasure-chest/HINTS.md @@ -0,0 +1,15 @@ +# Hints + +## 1. Define the `TreasureChest` opaque type. + +- Opaque types are defined using the `opaque` keyword. + +## 2. Define the `create` function. + +- The [`string.length` function][length] can be used to get the length of a string. + +## 3. Define `open` function. + +- The `==` operator can be used to compare two strings for equality. + +[length]: https://hexdocs.pm/gleam_stdlib/gleam/string.html#length \ No newline at end of file diff --git a/gleam/secure-treasure-chest/README.md b/gleam/secure-treasure-chest/README.md new file mode 100644 index 0000000..16769c7 --- /dev/null +++ b/gleam/secure-treasure-chest/README.md @@ -0,0 +1,79 @@ +# Secure Treasure Chest + +Welcome to Secure Treasure Chest on Exercism's Gleam Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +## Opaque Types + +Opaque types in Gleam are custom types where only the module that defines the type can construct or pattern match values of the type. This is useful for creating types that should only be used in a specific way. + +Opaque types are defined using the `opaque` keyword. + +```gleam +pub opaque type PositiveInt { + PositiveInt(inner: Int) +} +``` + +This `PositiveInt` type is to be used in situations where an int is wanted, but it has to be zero or greater. A regular Gleam `Int` could not be used as they can also be negative. + +The module that defines this type can define a function to get the inner int value, and a function for creating the `PositiveInt` type which will return an error if the value is not positive. + +```gleam +pub fn from_int(i: Int) -> Result(PositiveInt, String) { + case i { + _ if i < 0 -> Error("Value must be positive") + _ -> Ok(PositiveInt(i)) + } +} + +pub fn to_int(i: PositiveInt) -> Int { + i.inner +} +``` + +With this API other modules cannot construct a `PositiveInt` with a negative value, so any function that takes a `PositiveInt` can be sure that the value is positive. + +## Instructions + +Sharp eyed students will have noticed that the `TreasureChest` type in the previous exercise wasn't that secure! + +If you used the `get_treasure` function you had to supply the password, but you could still destructure the `TreasureChest` type to get the treasure without having to know the password. + +Let's fix that by using an Opaque Type. + +## 1. Define the `TreasureChest` opaque type. + +The `TreasureChest` contains two fields: +- A password that is a `String`. +- A treasure that is a generic type. + +The `TreasureChest` type must be opaque. + +## 2. Define the `create` function. + +This function takes two arguments: +- A password `String`. +- A treasure value of any type. + +The function returns a `TreasureChest` containing the password and the value. + +If the password is shorter than 8 characters then the function should return an error saying `Password must be at least 8 characters long`. + +## 3. Define `open` function. + +This function takes two arguments: + +- A `TreasureChest`. +- A password `String`. + +If the password matches the password in the `TreasureChest` then the function should return the treasure, otherwise it should return an error saying `Incorrect password`. + +## Source + +### Created by + +- @lpil \ No newline at end of file diff --git a/gleam/secure-treasure-chest/gleam.toml b/gleam/secure-treasure-chest/gleam.toml new file mode 100644 index 0000000..cb7bf25 --- /dev/null +++ b/gleam/secure-treasure-chest/gleam.toml @@ -0,0 +1,12 @@ +name = "secure_treasure_chest" +version = "0.1.0" + +[dependencies] +gleam_bitwise = "~> 1.2" +gleam_otp = "~> 0.7 or ~> 1.0" +gleam_stdlib = "~> 0.32 or ~> 1.0" +simplifile = "~> 1.0" +gleam_erlang = ">= 0.25.0 and < 1.0.0" + +[dev-dependencies] +exercism_test_runner = "~> 1.4" diff --git a/gleam/secure-treasure-chest/manifest.toml b/gleam/secure-treasure-chest/manifest.toml new file mode 100644 index 0000000..072155d --- /dev/null +++ b/gleam/secure-treasure-chest/manifest.toml @@ -0,0 +1,27 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "argv", version = "1.0.1", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "A6E9009E50BBE863EB37D963E4315398D41A3D87D0075480FC244125808F964A" }, + { name = "exercism_test_runner", version = "1.7.0", build_tools = ["gleam"], requirements = ["argv", "gap", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "exercism_test_runner", source = "hex", outer_checksum = "2FC1BADB19BEC2AE77BFD2D3A606A014C85412A7B874CAFC4BA8CF04B0B257CD" }, + { name = "gap", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "2EE1B0A17E85CF73A0C1D29DA315A2699117A8F549C8E8D89FA8261BE41EDEB1" }, + { name = "glance", version = "0.8.2", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "ACF09457E8B564AD7A0D823DAFDD326F58263C01ACB0D432A9BEFDEDD1DA8E73" }, + { name = "gleam_bitwise", version = "1.3.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_bitwise", source = "hex", outer_checksum = "B36E1D3188D7F594C7FD4F43D0D2CE17561DE896202017548578B16FE1FE9EFC" }, + { name = "gleam_community_ansi", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "FE79E08BF97009729259B6357EC058315B6FBB916FAD1C2FF9355115FEB0D3A4" }, + { name = "gleam_community_colour", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "A49A5E3AE8B637A5ACBA80ECB9B1AFE89FD3D5351FF6410A42B84F666D40D7D5" }, + { name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" }, + { name = "gleam_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" }, + { name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" }, + { name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" }, + { name = "glexer", version = "0.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "4484942A465482A0A100936E1E5F12314DB4B5AC0D87575A7B9E9062090B96BE" }, + { name = "simplifile", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "EB9AA8E65E5C1E3E0FDCFC81BC363FD433CB122D7D062750FFDF24DE4AC40116" }, + { name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" }, +] + +[requirements] +exercism_test_runner = { version = "~> 1.4" } +gleam_bitwise = { version = "~> 1.2" } +gleam_erlang = { version = ">= 0.25.0 and < 1.0.0"} +gleam_otp = { version = "~> 0.7 or ~> 1.0" } +gleam_stdlib = { version = "~> 0.32 or ~> 1.0" } +simplifile = { version = "~> 1.0" } diff --git a/gleam/secure-treasure-chest/src/secure_treasure_chest.gleam b/gleam/secure-treasure-chest/src/secure_treasure_chest.gleam new file mode 100644 index 0000000..2251b14 --- /dev/null +++ b/gleam/secure-treasure-chest/src/secure_treasure_chest.gleam @@ -0,0 +1,25 @@ +import gleam/string + +pub opaque type TreasureChest(t) { + TreasureChest(password: String, treasure: t) +} + +pub fn create( + password: String, + contents: treasure, +) -> Result(TreasureChest(treasure), String) { + case string.length(password) >= 8 { + True -> Ok(TreasureChest(password, contents)) + False -> Error("Password must be at least 8 characters long") + } +} + +pub fn open( + chest: TreasureChest(treasure), + password: String, +) -> Result(treasure, String) { + case password == chest.password { + True -> Ok(chest.treasure) + False -> Error("Incorrect password") + } +} diff --git a/gleam/secure-treasure-chest/test/secure_treasure_chest_test.gleam b/gleam/secure-treasure-chest/test/secure_treasure_chest_test.gleam new file mode 100644 index 0000000..1457883 --- /dev/null +++ b/gleam/secure-treasure-chest/test/secure_treasure_chest_test.gleam @@ -0,0 +1,55 @@ +import exercism/should +import exercism/test_runner +import gleam/string +import secure_treasure_chest +import simplifile + +pub fn main() { + test_runner.main() +} + +fn compact_whitespace(graphemes: List(String), acc: String) -> String { + case graphemes { + [] -> acc + [" ", " ", ..rest] -> compact_whitespace([" ", ..rest], acc) + [grapheme, ..rest] -> compact_whitespace(rest, acc <> grapheme) + } +} + +fn read_source() -> String { + let assert Ok(src) = simplifile.read("src/secure_treasure_chest.gleam") + compact_whitespace(string.to_graphemes(src), "") +} + +pub fn type_must_be_opaque_test() { + let src = read_source() + case string.contains(src, "pub opaque type TreasureChest") { + True -> Nil + False -> panic as "The TreasureChest type must exist and be opaque" + } +} + +pub fn create_is_ok_with_long_password_test() { + let assert Ok(_) = secure_treasure_chest.create("12345678", "My treasure") +} + +pub fn create_is_error_with_short_password_test() { + secure_treasure_chest.create("1234567", "My treasure") + |> should.equal(Error("Password must be at least 8 characters long")) +} + +pub fn open_is_ok_with_the_correct_password_test() { + let assert Ok(chest) = secure_treasure_chest.create("wwwibble", 100) + secure_treasure_chest.open(chest, "wwwibble") + |> should.equal(Ok(100)) + + let assert Ok(chest) = secure_treasure_chest.create("wwwobble", 1.5) + secure_treasure_chest.open(chest, "wwwobble") + |> should.equal(Ok(1.5)) +} + +pub fn open_is_an_error_with_an_incorrect_password_test() { + let assert Ok(chest) = secure_treasure_chest.create("wwwibble", 100) + secure_treasure_chest.open(chest, "wwwobble") + |> should.equal(Error("Incorrect password")) +}