Skip to content

Commit

Permalink
[gleam] complete secure-treasure-chest
Browse files Browse the repository at this point in the history
  • Loading branch information
joaofnds committed May 15, 2024
1 parent e7016f8 commit 769d0c0
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 0 deletions.
4 changes: 4 additions & 0 deletions gleam/secure-treasure-chest/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.beam
*.ez
build
erl_crash.dump
32 changes: 32 additions & 0 deletions gleam/secure-treasure-chest/HELP.md
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 15 additions & 0 deletions gleam/secure-treasure-chest/HINTS.md
Original file line number Diff line number Diff line change
@@ -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
79 changes: 79 additions & 0 deletions gleam/secure-treasure-chest/README.md
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions gleam/secure-treasure-chest/gleam.toml
Original file line number Diff line number Diff line change
@@ -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"
27 changes: 27 additions & 0 deletions gleam/secure-treasure-chest/manifest.toml
Original file line number Diff line number Diff line change
@@ -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" }
25 changes: 25 additions & 0 deletions gleam/secure-treasure-chest/src/secure_treasure_chest.gleam
Original file line number Diff line number Diff line change
@@ -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")
}
}
55 changes: 55 additions & 0 deletions gleam/secure-treasure-chest/test/secure_treasure_chest_test.gleam
Original file line number Diff line number Diff line change
@@ -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"))
}

0 comments on commit 769d0c0

Please sign in to comment.