From 6e7d9703ba7f2696e08e881dade735f42bd755f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sun, 9 Jun 2024 20:36:14 +0100 Subject: [PATCH] feat: Update the interface of `sentence.get()` and test. --- src/tatoeba/sentence.gleam | 66 +++++++++++++++++++------- test/sentence_test.gleam | 96 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 test/sentence_test.gleam diff --git a/src/tatoeba/sentence.gleam b/src/tatoeba/sentence.gleam index d72d576..fd9df58 100644 --- a/src/tatoeba/sentence.gleam +++ b/src/tatoeba/sentence.gleam @@ -396,10 +396,7 @@ pub type Sentence { correctness: Int, /// A list of `Translation`s of the sentence. translations: List(Translation), - /// ⚠️ It's not clear what this value represents. - /// - /// If you know what it is, please fill it in. - /// + // TODO(vxern): What does this value represent? /// Note: This value will always be `None` given that this API wrapper does not /// act as a user. user_sentences: Option(List(Unknown)), @@ -533,30 +530,63 @@ pub fn sentence( )) } +/// Represents the ID of a sentence in the Tatoeba corpus. +/// +pub opaque type SentenceId { + SentenceId(value: Int) +} + +/// Represents an error resulting from an invalid value being used for +/// creating an ID. +/// +pub type IdError { + /// Received an invalid input. + InvalidValueError(Int) +} + +/// Creates a new ID to be used for querying the Tatoeba corpus. +/// +pub fn new_id(id: Int) -> Result(SentenceId, IdError) { + case id { + negative if id < 0 || id == 0 -> Error(InvalidValueError(negative)) + _ -> Ok(SentenceId(id)) + } +} + +/// The possible errors sentence retrieval can fail with. +/// +pub type SentenceError { + /// Failed to make a request to Tatoeba. + RequestError(Dynamic) + /// Failed to decode the response data. + DecodeError(json.DecodeError) +} + /// Gets data of a single sentence in the Tatoeba corpus. /// -pub fn get(id id: Int) -> Result(Option(Sentence), String) { +pub fn get(id id: SentenceId) -> Result(Option(Sentence), SentenceError) { let request = - api.new_request_to("/sentence/" <> int.to_string(id)) + api.new_request_to("/sentence/" <> int.to_string(id.value)) |> request.set_method(http.Get) use response <- result.try( httpc.send(request) - |> result.map_error(fn(_) { "Failed to send request to Tatoeba." }), + |> result.map_error(fn(error) { RequestError(error) }), ) case response.body |> string.length() { 0 -> Ok(None) - _ -> { - use sentence <- result.try( - json.decode(response.body, sentence) - |> result.map_error(fn(error) { - "Failed to decode sentence data: " - <> dynamic.classify(dynamic.from(error)) - }), - ) - - Ok(Some(sentence)) - } + _ -> response.body |> decode_payload() |> result.map(Some) } } + +/// Decodes the received sentence payload. +/// +fn decode_payload(payload: String) -> Result(Sentence, SentenceError) { + use sentence <- result.try( + json.decode(payload, sentence) + |> result.map_error(fn(error) { DecodeError(error) }), + ) + + Ok(sentence) +} diff --git a/test/sentence_test.gleam b/test/sentence_test.gleam new file mode 100644 index 0000000..bb10d9c --- /dev/null +++ b/test/sentence_test.gleam @@ -0,0 +1,96 @@ +import gleam/option.{None, Some} +import gleeunit +import gleeunit/should +import tatoeba/sentence + +pub fn main() { + gleeunit.main() +} + +pub fn new_id_test() { + sentence.new_id(1) + |> should.be_ok() + + sentence.new_id(0) + |> should.be_error() + |> should.equal(sentence.InvalidValueError(0)) + + sentence.new_id(-1) + |> should.be_error() + |> should.equal(sentence.InvalidValueError(-1)) +} + +pub fn sentence_exists_test() { + let assert Ok(id) = sentence.new_id(12_212_258) + let result = sentence.get(id) + + result |> should.be_ok() + + let assert Ok(sentence) = result + + sentence |> should.be_some() +} + +pub fn failed_request_test() { + // TODO: Test this. + + Nil +} + +pub fn failed_decoding_test() { + // TODO: Test this. + + Nil +} + +pub fn sentence_removed_test() { + let assert Ok(id) = sentence.new_id(4_802_955) + let result = sentence.get(id) + + result |> should.be_ok() + + let assert Ok(sentence) = result + + should.be_none(sentence) +} + +pub fn sentence_test() { + let assert Ok(id) = sentence.new_id(12_212_258) + let assert Ok(Some(sentence)) = sentence.get(id) + + sentence.id |> should.equal(12_212_258) + sentence.text |> should.equal("This work is free of charge.") + sentence.language |> should.equal(Some("eng")) + sentence.language_tag |> should.equal("en") + sentence.language_name |> should.equal("English") + sentence.script |> should.equal(None) + sentence.license |> should.equal("CC BY 2.0 FR") + sentence.based_on_id |> should.equal(Some(5_686_783)) + sentence.correctness |> should.equal(0) + sentence.translations + |> should.equal([ + sentence.Translation( + id: 5_686_783, + text: "Această lucrare nu se plătește.", + language: Some("ron"), + language_tag: "ro", + language_name: "Romanian", + correctness: 0, + script: None, + transcriptions: [], + audios: [], + ), + ]) + sentence.user_sentences |> should.equal(None) + sentence.list_ids |> should.equal(None) + sentence.transcriptions |> should.equal([]) + sentence.audios |> should.equal([]) + sentence.owner + |> should.equal(Some(sentence.User(claimed_native: None, username: "vxern"))) + sentence.writing_direction |> should.equal(sentence.LeftToRight) + sentence.is_favorite |> should.equal(None) + sentence.is_owned_by_current_user |> should.equal(False) + sentence.permissions |> should.equal(None) + sentence.max_visible_translations |> should.equal(5) + sentence.current_user_review |> should.equal(None) +}