From fab4c9df5d3381f15a38f8c51a824e213b549e86 Mon Sep 17 00:00:00 2001 From: Lily Rose Date: Tue, 8 Jul 2025 23:03:42 +1000 Subject: [PATCH] Initial commit --- .github/workflows/test.yml | 23 + .gitignore | 4 + README.md | 24 + gleam.toml | 15 + manifest.toml | 17 + src/spacetraders_models/account.gleam | 30 ++ src/spacetraders_models/account_id.gleam | 33 ++ src/spacetraders_models/account_token.gleam | 31 ++ src/spacetraders_models/activity_level.gleam | 40 ++ src/spacetraders_models/agent.gleam | 36 ++ src/spacetraders_models/agent_event.gleam | 29 ++ src/spacetraders_models/agent_event_id.gleam | 33 ++ src/spacetraders_models/agent_symbol.gleam | 33 ++ src/spacetraders_models/agent_token.gleam | 31 ++ src/spacetraders_models/auth_method.gleam | 27 + src/spacetraders_models/chart.gleam | 26 + .../chart_transaction.gleam | 30 ++ src/spacetraders_models/constellation.gleam | 33 ++ src/spacetraders_models/construction.gleam | 21 + .../construction_material.gleam | 13 + src/spacetraders_models/contract.gleam | 43 ++ .../contract_deliver_good.gleam | 28 ++ src/spacetraders_models/contract_id.gleam | 33 ++ .../contract_payment.gleam | 11 + src/spacetraders_models/contract_terms.gleam | 25 + src/spacetraders_models/contract_type.gleam | 37 ++ src/spacetraders_models/cooldown.gleam | 31 ++ src/spacetraders_models/crew_rotation.gleam | 34 ++ src/spacetraders_models/engine_symbol.gleam | 40 ++ src/spacetraders_models/extraction.gleam | 13 + .../extraction_yield.gleam | 12 + src/spacetraders_models/faction.gleam | 37 ++ src/spacetraders_models/faction_symbol.gleam | 85 ++++ src/spacetraders_models/faction_trait.gleam | 13 + .../faction_trait_symbol.gleam | 205 ++++++++ src/spacetraders_models/frame_symbol.gleam | 76 +++ src/spacetraders_models/internal/jwt.gleam | 143 ++++++ src/spacetraders_models/internal/time.gleam | 219 +++++++++ src/spacetraders_models/jump_gate.gleam | 15 + src/spacetraders_models/market.gleam | 42 ++ src/spacetraders_models/market_symbol.gleam | 33 ++ .../market_trade_good.gleam | 41 ++ .../market_transaction.gleam | 44 ++ src/spacetraders_models/meta.gleam | 12 + src/spacetraders_models/module_symbol.gleam | 88 ++++ src/spacetraders_models/mount_deposit.gleam | 70 +++ src/spacetraders_models/mount_symbol.gleam | 73 +++ src/spacetraders_models/paged_data.gleam | 12 + src/spacetraders_models/public_agent.gleam | 32 ++ src/spacetraders_models/reactor_symbol.gleam | 43 ++ .../refinement_produce.gleam | 55 +++ .../refinement_yield.gleam | 12 + .../repair_transaction.gleam | 30 ++ src/spacetraders_models/scanned_ship.gleam | 52 ++ .../scanned_ship_engine.gleam | 11 + .../scanned_ship_frame.gleam | 11 + .../scanned_ship_mount.gleam | 11 + .../scanned_ship_reactor.gleam | 11 + src/spacetraders_models/scanned_system.gleam | 32 ++ .../scanned_waypoint.gleam | 57 +++ .../scrap_transaction.gleam | 30 ++ src/spacetraders_models/sector_symbol.gleam | 33 ++ src/spacetraders_models/ship.gleam | 59 +++ src/spacetraders_models/ship_cargo.gleam | 16 + src/spacetraders_models/ship_cargo_item.gleam | 19 + src/spacetraders_models/ship_component.gleam | 37 ++ .../ship_component_condition.gleam | 40 ++ .../ship_component_integrity.gleam | 40 ++ .../ship_component_quality.gleam | 28 ++ .../ship_condition_event.gleam | 22 + .../ship_condition_event_symbol.gleam | 112 +++++ src/spacetraders_models/ship_crew.gleam | 30 ++ src/spacetraders_models/ship_engine.gleam | 40 ++ src/spacetraders_models/ship_frame.gleam | 46 ++ src/spacetraders_models/ship_fuel.gleam | 18 + .../ship_fuel_consumed.gleam | 13 + .../ship_modification_transaction.gleam | 34 ++ src/spacetraders_models/ship_module.gleam | 40 ++ src/spacetraders_models/ship_mount.gleam | 41 ++ src/spacetraders_models/ship_nav.gleam | 34 ++ .../ship_nav_flight_mode.gleam | 40 ++ src/spacetraders_models/ship_nav_route.gleam | 27 + .../ship_nav_route_waypoint.gleam | 23 + src/spacetraders_models/ship_nav_status.gleam | 37 ++ src/spacetraders_models/ship_reactor.gleam | 40 ++ .../ship_registration.gleam | 19 + .../ship_requirements.gleam | 25 + src/spacetraders_models/ship_role.gleam | 70 +++ src/spacetraders_models/ship_symbol.gleam | 33 ++ src/spacetraders_models/ship_type.gleam | 43 ++ src/spacetraders_models/shipyard.gleam | 39 ++ src/spacetraders_models/shipyard_ship.gleam | 61 +++ .../shipyard_ship_crew.gleam | 11 + src/spacetraders_models/shipyard_symbol.gleam | 33 ++ .../shipyard_transaction.gleam | 34 ++ src/spacetraders_models/siphon.gleam | 13 + src/spacetraders_models/siphon_yield.gleam | 12 + .../spacetraders_error.gleam | 28 ++ src/spacetraders_models/supply_level.gleam | 43 ++ src/spacetraders_models/survey.gleam | 44 ++ src/spacetraders_models/survey_deposit.gleam | 16 + .../survey_signature.gleam | 33 ++ src/spacetraders_models/survey_size.gleam | 37 ++ src/spacetraders_models/system.gleam | 59 +++ src/spacetraders_models/system_faction.gleam | 11 + src/spacetraders_models/system_symbol.gleam | 33 ++ src/spacetraders_models/system_type.gleam | 58 +++ src/spacetraders_models/system_waypoint.gleam | 33 ++ .../token_parse_error.gleam | 4 + src/spacetraders_models/trade_good.gleam | 13 + src/spacetraders_models/trade_good_type.gleam | 37 ++ src/spacetraders_models/trade_symbol.gleam | 463 ++++++++++++++++++ .../transaction_type.gleam | 34 ++ src/spacetraders_models/waypoint.gleam | 75 +++ .../waypoint_faction.gleam | 11 + .../waypoint_modifier.gleam | 17 + .../waypoint_modifier_symbol.gleam | 43 ++ .../waypoint_orbital.gleam | 11 + src/spacetraders_models/waypoint_symbol.gleam | 33 ++ src/spacetraders_models/waypoint_trait.gleam | 13 + .../waypoint_trait_symbol.gleam | 235 +++++++++ src/spacetraders_models/waypoint_type.gleam | 70 +++ test/spacetraders_models_test.gleam | 5 + 123 files changed, 5152 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 gleam.toml create mode 100644 manifest.toml create mode 100644 src/spacetraders_models/account.gleam create mode 100644 src/spacetraders_models/account_id.gleam create mode 100644 src/spacetraders_models/account_token.gleam create mode 100644 src/spacetraders_models/activity_level.gleam create mode 100644 src/spacetraders_models/agent.gleam create mode 100644 src/spacetraders_models/agent_event.gleam create mode 100644 src/spacetraders_models/agent_event_id.gleam create mode 100644 src/spacetraders_models/agent_symbol.gleam create mode 100644 src/spacetraders_models/agent_token.gleam create mode 100644 src/spacetraders_models/auth_method.gleam create mode 100644 src/spacetraders_models/chart.gleam create mode 100644 src/spacetraders_models/chart_transaction.gleam create mode 100644 src/spacetraders_models/constellation.gleam create mode 100644 src/spacetraders_models/construction.gleam create mode 100644 src/spacetraders_models/construction_material.gleam create mode 100644 src/spacetraders_models/contract.gleam create mode 100644 src/spacetraders_models/contract_deliver_good.gleam create mode 100644 src/spacetraders_models/contract_id.gleam create mode 100644 src/spacetraders_models/contract_payment.gleam create mode 100644 src/spacetraders_models/contract_terms.gleam create mode 100644 src/spacetraders_models/contract_type.gleam create mode 100644 src/spacetraders_models/cooldown.gleam create mode 100644 src/spacetraders_models/crew_rotation.gleam create mode 100644 src/spacetraders_models/engine_symbol.gleam create mode 100644 src/spacetraders_models/extraction.gleam create mode 100644 src/spacetraders_models/extraction_yield.gleam create mode 100644 src/spacetraders_models/faction.gleam create mode 100644 src/spacetraders_models/faction_symbol.gleam create mode 100644 src/spacetraders_models/faction_trait.gleam create mode 100644 src/spacetraders_models/faction_trait_symbol.gleam create mode 100644 src/spacetraders_models/frame_symbol.gleam create mode 100644 src/spacetraders_models/internal/jwt.gleam create mode 100644 src/spacetraders_models/internal/time.gleam create mode 100644 src/spacetraders_models/jump_gate.gleam create mode 100644 src/spacetraders_models/market.gleam create mode 100644 src/spacetraders_models/market_symbol.gleam create mode 100644 src/spacetraders_models/market_trade_good.gleam create mode 100644 src/spacetraders_models/market_transaction.gleam create mode 100644 src/spacetraders_models/meta.gleam create mode 100644 src/spacetraders_models/module_symbol.gleam create mode 100644 src/spacetraders_models/mount_deposit.gleam create mode 100644 src/spacetraders_models/mount_symbol.gleam create mode 100644 src/spacetraders_models/paged_data.gleam create mode 100644 src/spacetraders_models/public_agent.gleam create mode 100644 src/spacetraders_models/reactor_symbol.gleam create mode 100644 src/spacetraders_models/refinement_produce.gleam create mode 100644 src/spacetraders_models/refinement_yield.gleam create mode 100644 src/spacetraders_models/repair_transaction.gleam create mode 100644 src/spacetraders_models/scanned_ship.gleam create mode 100644 src/spacetraders_models/scanned_ship_engine.gleam create mode 100644 src/spacetraders_models/scanned_ship_frame.gleam create mode 100644 src/spacetraders_models/scanned_ship_mount.gleam create mode 100644 src/spacetraders_models/scanned_ship_reactor.gleam create mode 100644 src/spacetraders_models/scanned_system.gleam create mode 100644 src/spacetraders_models/scanned_waypoint.gleam create mode 100644 src/spacetraders_models/scrap_transaction.gleam create mode 100644 src/spacetraders_models/sector_symbol.gleam create mode 100644 src/spacetraders_models/ship.gleam create mode 100644 src/spacetraders_models/ship_cargo.gleam create mode 100644 src/spacetraders_models/ship_cargo_item.gleam create mode 100644 src/spacetraders_models/ship_component.gleam create mode 100644 src/spacetraders_models/ship_component_condition.gleam create mode 100644 src/spacetraders_models/ship_component_integrity.gleam create mode 100644 src/spacetraders_models/ship_component_quality.gleam create mode 100644 src/spacetraders_models/ship_condition_event.gleam create mode 100644 src/spacetraders_models/ship_condition_event_symbol.gleam create mode 100644 src/spacetraders_models/ship_crew.gleam create mode 100644 src/spacetraders_models/ship_engine.gleam create mode 100644 src/spacetraders_models/ship_frame.gleam create mode 100644 src/spacetraders_models/ship_fuel.gleam create mode 100644 src/spacetraders_models/ship_fuel_consumed.gleam create mode 100644 src/spacetraders_models/ship_modification_transaction.gleam create mode 100644 src/spacetraders_models/ship_module.gleam create mode 100644 src/spacetraders_models/ship_mount.gleam create mode 100644 src/spacetraders_models/ship_nav.gleam create mode 100644 src/spacetraders_models/ship_nav_flight_mode.gleam create mode 100644 src/spacetraders_models/ship_nav_route.gleam create mode 100644 src/spacetraders_models/ship_nav_route_waypoint.gleam create mode 100644 src/spacetraders_models/ship_nav_status.gleam create mode 100644 src/spacetraders_models/ship_reactor.gleam create mode 100644 src/spacetraders_models/ship_registration.gleam create mode 100644 src/spacetraders_models/ship_requirements.gleam create mode 100644 src/spacetraders_models/ship_role.gleam create mode 100644 src/spacetraders_models/ship_symbol.gleam create mode 100644 src/spacetraders_models/ship_type.gleam create mode 100644 src/spacetraders_models/shipyard.gleam create mode 100644 src/spacetraders_models/shipyard_ship.gleam create mode 100644 src/spacetraders_models/shipyard_ship_crew.gleam create mode 100644 src/spacetraders_models/shipyard_symbol.gleam create mode 100644 src/spacetraders_models/shipyard_transaction.gleam create mode 100644 src/spacetraders_models/siphon.gleam create mode 100644 src/spacetraders_models/siphon_yield.gleam create mode 100644 src/spacetraders_models/spacetraders_error.gleam create mode 100644 src/spacetraders_models/supply_level.gleam create mode 100644 src/spacetraders_models/survey.gleam create mode 100644 src/spacetraders_models/survey_deposit.gleam create mode 100644 src/spacetraders_models/survey_signature.gleam create mode 100644 src/spacetraders_models/survey_size.gleam create mode 100644 src/spacetraders_models/system.gleam create mode 100644 src/spacetraders_models/system_faction.gleam create mode 100644 src/spacetraders_models/system_symbol.gleam create mode 100644 src/spacetraders_models/system_type.gleam create mode 100644 src/spacetraders_models/system_waypoint.gleam create mode 100644 src/spacetraders_models/token_parse_error.gleam create mode 100644 src/spacetraders_models/trade_good.gleam create mode 100644 src/spacetraders_models/trade_good_type.gleam create mode 100644 src/spacetraders_models/trade_symbol.gleam create mode 100644 src/spacetraders_models/transaction_type.gleam create mode 100644 src/spacetraders_models/waypoint.gleam create mode 100644 src/spacetraders_models/waypoint_faction.gleam create mode 100644 src/spacetraders_models/waypoint_modifier.gleam create mode 100644 src/spacetraders_models/waypoint_modifier_symbol.gleam create mode 100644 src/spacetraders_models/waypoint_orbital.gleam create mode 100644 src/spacetraders_models/waypoint_symbol.gleam create mode 100644 src/spacetraders_models/waypoint_trait.gleam create mode 100644 src/spacetraders_models/waypoint_trait_symbol.gleam create mode 100644 src/spacetraders_models/waypoint_type.gleam create mode 100644 test/spacetraders_models_test.gleam diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7c92c48 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: test + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + otp-version: "27.1.2" + gleam-version: "1.11.1" + rebar3-version: "3" + # elixir-version: "1" + - run: gleam deps download + - run: gleam test + - run: gleam format --check src test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..599be4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +/build +erl_crash.dump diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa70cda --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# spacetraders_models + +[![Package Version](https://img.shields.io/hexpm/v/spacetraders_models)](https://hex.pm/packages/spacetraders_models) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/spacetraders_models/) + +```sh +gleam add spacetraders_models@1 +``` +```gleam +import spacetraders_models + +pub fn main() -> Nil { + // TODO: An example of the project in use +} +``` + +Further documentation can be found at . + +## Development + +```sh +gleam run # Run the project +gleam test # Run the tests +``` diff --git a/gleam.toml b/gleam.toml new file mode 100644 index 0000000..4ea9fc8 --- /dev/null +++ b/gleam.toml @@ -0,0 +1,15 @@ +name = "spacetraders_models" +version = "1.0.0" +gleam = ">= 1.11.0" +description = "Gleam types for the spacetraders.io game API" +licences = ["MIT"] +repository = { type = "forgejo", host = "git.7cs.dev", user = "lily", repo = "gleam-spacetraders-models" } + +[dependencies] +gleam_stdlib = ">= 0.44.0 and < 2.0.0" +gleam_http = ">= 4.1.0 and < 5.0.0" +gleam_time = ">= 1.2.0 and < 2.0.0" +gleam_json = ">= 3.0.2 and < 4.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..1c04d57 --- /dev/null +++ b/manifest.toml @@ -0,0 +1,17 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "gleam_http", version = "4.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "DB25DFC8530B64B77105405B80686541A0D96F7E2D83D807D6B2155FB9A8B1B8" }, + { name = "gleam_json", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "874FA3C3BB6E22DD2BB111966BD40B3759E9094E05257899A7C08F5DE77EC049" }, + { name = "gleam_stdlib", version = "0.61.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "3DC407D6EDA98FCE089150C11F3AD892B6F4C3CA77C87A97BAE8D5AB5E41F331" }, + { name = "gleam_time", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "D71F1AFF7FEB534FF55E5DC58E534E9201BA75A444619788A2E4DEA4EBD87D16" }, + { name = "gleeunit", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "63022D81C12C17B7F1A60E029964E830A4CBD846BBC6740004FC1F1031AE0326" }, +] + +[requirements] +gleam_http = { version = ">= 4.1.0 and < 5.0.0" } +gleam_json = { version = ">= 3.0.2 and < 4.0.0" } +gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } +gleam_time = { version = ">= 1.2.0 and < 2.0.0" } +gleeunit = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/src/spacetraders_models/account.gleam b/src/spacetraders_models/account.gleam new file mode 100644 index 0000000..e2dd366 --- /dev/null +++ b/src/spacetraders_models/account.gleam @@ -0,0 +1,30 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/account_id.{type AccountId} +import spacetraders_models/internal/time + +pub type Account { + Account( + id: AccountId, + email: Option(String), + token: Option(String), + created_at: Timestamp, + ) +} + +pub fn decoder() -> Decoder(Account) { + use id <- decode.field("id", account_id.decoder()) + use email <- decode.optional_field( + "email", + option.None, + decode.optional(decode.string), + ) + use token <- decode.optional_field( + "token", + option.None, + decode.optional(decode.string), + ) + use created_at <- decode.field("createdAt", time.rfc3339_timestamp_decoder()) + decode.success(Account(id:, email:, token:, created_at:)) +} diff --git a/src/spacetraders_models/account_id.gleam b/src/spacetraders_models/account_id.gleam new file mode 100644 index 0000000..94f6dfb --- /dev/null +++ b/src/spacetraders_models/account_id.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type AccountId { + AccountId(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(AccountId, Nil) { + case string.length(value) >= min_length { + True -> Ok(AccountId(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(AccountId) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(account_id) -> decode.success(account_id) + Error(Nil) -> decode.failure(AccountId("invalid"), "AccountId") + } +} + +pub fn to_string(account_id: AccountId) -> String { + let AccountId(symbol) = account_id + symbol +} + +pub fn encode(account_id: AccountId) -> Json { + json.string(to_string(account_id)) +} diff --git a/src/spacetraders_models/account_token.gleam b/src/spacetraders_models/account_token.gleam new file mode 100644 index 0000000..674d410 --- /dev/null +++ b/src/spacetraders_models/account_token.gleam @@ -0,0 +1,31 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/result +import spacetraders_models/internal/jwt +import spacetraders_models/token_parse_error.{ + type TokenParseError, IncorrectType, InvalidToken, +} + +pub opaque type AccountToken { + AccountToken(String) +} + +pub fn parse(value: String) -> Result(AccountToken, TokenParseError) { + use jwt <- result.try(jwt.parse(value) |> result.replace_error(InvalidToken)) + case jwt.payload.subject { + "account-token" -> Ok(AccountToken(value)) + _ -> Error(IncorrectType) + } +} + +pub fn decoder() -> Decoder(AccountToken) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(token) -> decode.success(token) + Error(_) -> decode.failure(AccountToken("invalid"), "AccountToken") + } +} + +pub fn to_string(account_token: AccountToken) -> String { + let AccountToken(value) = account_token + value +} diff --git a/src/spacetraders_models/activity_level.gleam b/src/spacetraders_models/activity_level.gleam new file mode 100644 index 0000000..45481a6 --- /dev/null +++ b/src/spacetraders_models/activity_level.gleam @@ -0,0 +1,40 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type ActivityLevel { + Weak + Growing + Strong + Restricted +} + +pub fn parse(value: String) -> Result(ActivityLevel, Nil) { + case value { + "WEAK" -> Ok(Weak) + "GROWING" -> Ok(Growing) + "STRONG" -> Ok(Strong) + "RESTRICTED" -> Ok(Restricted) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ActivityLevel) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(activity_level) -> decode.success(activity_level) + Error(Nil) -> decode.failure(Weak, "ActivityLevel") + } +} + +pub fn to_string(activity_level: ActivityLevel) -> String { + case activity_level { + Weak -> "WEAK" + Growing -> "GROWING" + Strong -> "STRONG" + Restricted -> "RESTRICTED" + } +} + +pub fn encode(activity_level: ActivityLevel) -> Json { + json.string(to_string(activity_level)) +} diff --git a/src/spacetraders_models/agent.gleam b/src/spacetraders_models/agent.gleam new file mode 100644 index 0000000..fbe363b --- /dev/null +++ b/src/spacetraders_models/agent.gleam @@ -0,0 +1,36 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/account_id.{type AccountId} +import spacetraders_models/agent_symbol.{type AgentSymbol} +import spacetraders_models/faction_symbol.{type FactionSymbol} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type Agent { + Agent( + account_id: AccountId, + symbol: AgentSymbol, + headquarters: WaypointSymbol, + credits: Int, + starting_faction: FactionSymbol, + ship_count: Int, + ) +} + +pub fn decoder() -> Decoder(Agent) { + use account_id <- decode.field("accountId", account_id.decoder()) + use symbol <- decode.field("symbol", agent_symbol.decoder()) + use headquarters <- decode.field("headquarters", waypoint_symbol.decoder()) + use credits <- decode.field("credits", decode.int) + use starting_faction <- decode.field( + "startingFaction", + faction_symbol.decoder(), + ) + use ship_count <- decode.field("shipCount", decode.int) + decode.success(Agent( + account_id:, + symbol:, + headquarters:, + credits:, + starting_faction:, + ship_count:, + )) +} diff --git a/src/spacetraders_models/agent_event.gleam b/src/spacetraders_models/agent_event.gleam new file mode 100644 index 0000000..3f6b1ce --- /dev/null +++ b/src/spacetraders_models/agent_event.gleam @@ -0,0 +1,29 @@ +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/agent_event_id.{type AgentEventId} +import spacetraders_models/internal/time + +pub type AgentEvent { + AgentEvent( + id: AgentEventId, + type_: String, + message: String, + data: Option(Dynamic), + created_at: Timestamp, + ) +} + +pub fn decoder() -> Decoder(AgentEvent) { + use id <- decode.field("id", agent_event_id.decoder()) + use type_ <- decode.field("type", decode.string) + use message <- decode.field("message", decode.string) + use data <- decode.optional_field( + "data", + option.None, + decode.optional(decode.dynamic), + ) + use created_at <- decode.field("createdAt", time.rfc3339_timestamp_decoder()) + decode.success(AgentEvent(id:, type_:, message:, data:, created_at:)) +} diff --git a/src/spacetraders_models/agent_event_id.gleam b/src/spacetraders_models/agent_event_id.gleam new file mode 100644 index 0000000..1fb391c --- /dev/null +++ b/src/spacetraders_models/agent_event_id.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type AgentEventId { + AgentEventId(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(AgentEventId, Nil) { + case string.length(value) >= min_length { + True -> Ok(AgentEventId(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(AgentEventId) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(agent_event_id) -> decode.success(agent_event_id) + Error(Nil) -> decode.failure(AgentEventId("invalid"), "AgentEventId") + } +} + +pub fn to_string(agent_event_id: AgentEventId) -> String { + let AgentEventId(symbol) = agent_event_id + symbol +} + +pub fn encode(agent_event_id: AgentEventId) -> Json { + json.string(to_string(agent_event_id)) +} diff --git a/src/spacetraders_models/agent_symbol.gleam b/src/spacetraders_models/agent_symbol.gleam new file mode 100644 index 0000000..3d5f9d2 --- /dev/null +++ b/src/spacetraders_models/agent_symbol.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type AgentSymbol { + AgentSymbol(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(AgentSymbol, Nil) { + case string.length(value) >= min_length { + True -> Ok(AgentSymbol(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(AgentSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(agent_symbol) -> decode.success(agent_symbol) + Error(Nil) -> decode.failure(AgentSymbol("invalid"), "AgentSymbol") + } +} + +pub fn to_string(agent_symbol: AgentSymbol) -> String { + let AgentSymbol(symbol) = agent_symbol + symbol +} + +pub fn encode(agent_symbol: AgentSymbol) -> Json { + json.string(to_string(agent_symbol)) +} diff --git a/src/spacetraders_models/agent_token.gleam b/src/spacetraders_models/agent_token.gleam new file mode 100644 index 0000000..0713b3c --- /dev/null +++ b/src/spacetraders_models/agent_token.gleam @@ -0,0 +1,31 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/result +import spacetraders_models/internal/jwt +import spacetraders_models/token_parse_error.{ + type TokenParseError, IncorrectType, InvalidToken, +} + +pub opaque type AgentToken { + AgentToken(String) +} + +pub fn parse(value: String) -> Result(AgentToken, TokenParseError) { + use jwt <- result.try(jwt.parse(value) |> result.replace_error(InvalidToken)) + case jwt.payload.subject { + "agent-token" -> Ok(AgentToken(value)) + _ -> Error(IncorrectType) + } +} + +pub fn decoder() -> Decoder(AgentToken) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(token) -> decode.success(token) + Error(_) -> decode.failure(AgentToken("invalid"), "AgentToken") + } +} + +pub fn to_string(agent_token: AgentToken) -> String { + let AgentToken(value) = agent_token + value +} diff --git a/src/spacetraders_models/auth_method.gleam b/src/spacetraders_models/auth_method.gleam new file mode 100644 index 0000000..ce2e490 --- /dev/null +++ b/src/spacetraders_models/auth_method.gleam @@ -0,0 +1,27 @@ +import gleam/http/request.{type Request} +import spacetraders_models/account_token.{type AccountToken} +import spacetraders_models/agent_token.{type AgentToken} + +pub type AuthMethod { + AccountAuth(AccountToken) + AgentAuth(AgentToken) + NoAuth +} + +pub fn apply(req: Request(a), auth_method: AuthMethod) -> Request(a) { + case auth_method { + NoAuth -> req + AccountAuth(account_token) -> + req + |> request.set_header( + "Authorization", + "Bearer " <> account_token.to_string(account_token), + ) + AgentAuth(agent_token) -> + req + |> request.set_header( + "Authorization", + "Bearer " <> agent_token.to_string(agent_token), + ) + } +} diff --git a/src/spacetraders_models/chart.gleam b/src/spacetraders_models/chart.gleam new file mode 100644 index 0000000..18dd799 --- /dev/null +++ b/src/spacetraders_models/chart.gleam @@ -0,0 +1,26 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/agent_symbol.{type AgentSymbol} +import spacetraders_models/internal/time +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type Chart { + Chart( + waypoint_symbol: WaypointSymbol, + submitted_by: AgentSymbol, + submitted_on: Timestamp, + ) +} + +pub fn decoder() -> Decoder(Chart) { + use waypoint_symbol <- decode.field( + "waypointSymbol", + waypoint_symbol.decoder(), + ) + use submitted_by <- decode.field("submittedBy", agent_symbol.decoder()) + use submitted_on <- decode.field( + "submittedOn", + time.rfc3339_timestamp_decoder(), + ) + decode.success(Chart(waypoint_symbol:, submitted_by:, submitted_on:)) +} diff --git a/src/spacetraders_models/chart_transaction.gleam b/src/spacetraders_models/chart_transaction.gleam new file mode 100644 index 0000000..b6b0de6 --- /dev/null +++ b/src/spacetraders_models/chart_transaction.gleam @@ -0,0 +1,30 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/internal/time +import spacetraders_models/ship_symbol.{type ShipSymbol} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type ChartTransaction { + ChartTransaction( + waypoint_symbol: WaypointSymbol, + ship_symbol: ShipSymbol, + total_price: Int, + timestamp: Timestamp, + ) +} + +pub fn decoder() -> Decoder(ChartTransaction) { + use waypoint_symbol <- decode.field( + "waypointSymbol", + waypoint_symbol.decoder(), + ) + use ship_symbol <- decode.field("shipSymbol", ship_symbol.decoder()) + use total_price <- decode.field("totalPrice", decode.int) + use timestamp <- decode.field("timestamp", time.rfc3339_timestamp_decoder()) + decode.success(ChartTransaction( + waypoint_symbol:, + ship_symbol:, + total_price:, + timestamp:, + )) +} diff --git a/src/spacetraders_models/constellation.gleam b/src/spacetraders_models/constellation.gleam new file mode 100644 index 0000000..bd60953 --- /dev/null +++ b/src/spacetraders_models/constellation.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type Constellation { + Constellation(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(Constellation, Nil) { + case string.length(value) >= min_length { + True -> Ok(Constellation(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(Constellation) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(constellation) -> decode.success(constellation) + Error(Nil) -> decode.failure(Constellation("invalid"), "Constellation") + } +} + +pub fn to_string(constellation: Constellation) -> String { + let Constellation(value) = constellation + value +} + +pub fn encode(constellation: Constellation) -> Json { + json.string(to_string(constellation)) +} diff --git a/src/spacetraders_models/construction.gleam b/src/spacetraders_models/construction.gleam new file mode 100644 index 0000000..6381c34 --- /dev/null +++ b/src/spacetraders_models/construction.gleam @@ -0,0 +1,21 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/construction_material.{type ConstructionMaterial} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type Construction { + Construction( + symbol: WaypointSymbol, + materials: List(ConstructionMaterial), + is_complete: Bool, + ) +} + +pub fn decoder() -> Decoder(Construction) { + use symbol <- decode.field("symbol", waypoint_symbol.decoder()) + use materials <- decode.field( + "materials", + decode.list(construction_material.decoder()), + ) + use is_complete <- decode.field("isComplete", decode.bool) + decode.success(Construction(symbol:, materials:, is_complete:)) +} diff --git a/src/spacetraders_models/construction_material.gleam b/src/spacetraders_models/construction_material.gleam new file mode 100644 index 0000000..b109204 --- /dev/null +++ b/src/spacetraders_models/construction_material.gleam @@ -0,0 +1,13 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/trade_symbol.{type TradeSymbol} + +pub type ConstructionMaterial { + ConstructionMaterial(trade_symbol: TradeSymbol, required: Int, fulfilled: Int) +} + +pub fn decoder() -> Decoder(ConstructionMaterial) { + use trade_symbol <- decode.field("tradeSymbol", trade_symbol.decoder()) + use required <- decode.field("required", decode.int) + use fulfilled <- decode.field("fulfilled", decode.int) + decode.success(ConstructionMaterial(trade_symbol:, required:, fulfilled:)) +} diff --git a/src/spacetraders_models/contract.gleam b/src/spacetraders_models/contract.gleam new file mode 100644 index 0000000..d332110 --- /dev/null +++ b/src/spacetraders_models/contract.gleam @@ -0,0 +1,43 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/contract_id.{type ContractId} +import spacetraders_models/contract_terms.{type ContractTerms} +import spacetraders_models/contract_type.{type ContractType} +import spacetraders_models/faction_symbol.{type FactionSymbol} +import spacetraders_models/internal/time + +pub type Contract { + Contract( + id: ContractId, + faction_symbol: FactionSymbol, + type_: ContractType, + terms: ContractTerms, + accepted: Bool, + fulfilled: Bool, + deadline_to_accept: Option(Timestamp), + ) +} + +pub fn decoder() -> Decoder(Contract) { + use id <- decode.field("id", contract_id.decoder()) + use faction_symbol <- decode.field("factionSymbol", faction_symbol.decoder()) + use type_ <- decode.field("type", contract_type.decoder()) + use terms <- decode.field("terms", contract_terms.decoder()) + use accepted <- decode.field("accepted", decode.bool) + use fulfilled <- decode.field("fulfilled", decode.bool) + use deadline_to_accept <- decode.optional_field( + "deadlineToAccept", + option.None, + decode.optional(time.rfc3339_timestamp_decoder()), + ) + decode.success(Contract( + id:, + faction_symbol:, + type_:, + terms:, + accepted:, + fulfilled:, + deadline_to_accept:, + )) +} diff --git a/src/spacetraders_models/contract_deliver_good.gleam b/src/spacetraders_models/contract_deliver_good.gleam new file mode 100644 index 0000000..b0d27bb --- /dev/null +++ b/src/spacetraders_models/contract_deliver_good.gleam @@ -0,0 +1,28 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/trade_symbol.{type TradeSymbol} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type ContractDeliverGood { + ContractDeliverGood( + trade_symbol: TradeSymbol, + destination_symbol: WaypointSymbol, + units_required: Int, + units_fulfilled: Int, + ) +} + +pub fn decoder() -> Decoder(ContractDeliverGood) { + use trade_symbol <- decode.field("tradeSymbol", trade_symbol.decoder()) + use destination_symbol <- decode.field( + "destinationSymbol", + waypoint_symbol.decoder(), + ) + use units_required <- decode.field("unitsRequired", decode.int) + use units_fulfilled <- decode.field("unitsFulfilled", decode.int) + decode.success(ContractDeliverGood( + trade_symbol:, + destination_symbol:, + units_required:, + units_fulfilled:, + )) +} diff --git a/src/spacetraders_models/contract_id.gleam b/src/spacetraders_models/contract_id.gleam new file mode 100644 index 0000000..1e37156 --- /dev/null +++ b/src/spacetraders_models/contract_id.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type ContractId { + ContractId(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(ContractId, Nil) { + case string.length(value) >= min_length { + True -> Ok(ContractId(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ContractId) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(contract_id) -> decode.success(contract_id) + Error(Nil) -> decode.failure(ContractId("invalid"), "ContractId") + } +} + +pub fn to_string(contract_id: ContractId) -> String { + let ContractId(symbol) = contract_id + symbol +} + +pub fn encode(contract_id: ContractId) -> Json { + json.string(to_string(contract_id)) +} diff --git a/src/spacetraders_models/contract_payment.gleam b/src/spacetraders_models/contract_payment.gleam new file mode 100644 index 0000000..927b019 --- /dev/null +++ b/src/spacetraders_models/contract_payment.gleam @@ -0,0 +1,11 @@ +import gleam/dynamic/decode.{type Decoder} + +pub type ContractPayment { + ContractPayment(on_accepted: Int, on_fulfilled: Int) +} + +pub fn decoder() -> Decoder(ContractPayment) { + use on_accepted <- decode.field("onAccepted", decode.int) + use on_fulfilled <- decode.field("onFulfilled", decode.int) + decode.success(ContractPayment(on_accepted:, on_fulfilled:)) +} diff --git a/src/spacetraders_models/contract_terms.gleam b/src/spacetraders_models/contract_terms.gleam new file mode 100644 index 0000000..46752db --- /dev/null +++ b/src/spacetraders_models/contract_terms.gleam @@ -0,0 +1,25 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/contract_deliver_good.{type ContractDeliverGood} +import spacetraders_models/contract_payment.{type ContractPayment} +import spacetraders_models/internal/time + +pub type ContractTerms { + ContractTerms( + deadline: Timestamp, + payment: ContractPayment, + deliver: Option(List(ContractDeliverGood)), + ) +} + +pub fn decoder() -> Decoder(ContractTerms) { + use deadline <- decode.field("deadline", time.rfc3339_timestamp_decoder()) + use payment <- decode.field("payment", contract_payment.decoder()) + use deliver <- decode.optional_field( + "deliver", + option.None, + decode.optional(decode.list(contract_deliver_good.decoder())), + ) + decode.success(ContractTerms(deadline:, payment:, deliver:)) +} diff --git a/src/spacetraders_models/contract_type.gleam b/src/spacetraders_models/contract_type.gleam new file mode 100644 index 0000000..4d459a7 --- /dev/null +++ b/src/spacetraders_models/contract_type.gleam @@ -0,0 +1,37 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type ContractType { + Procurement + Transport + Shuttle +} + +pub fn parse(value: String) -> Result(ContractType, Nil) { + case value { + "PROCUREMENT" -> Ok(Procurement) + "TRANSPORT" -> Ok(Transport) + "SHUTTLE" -> Ok(Shuttle) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ContractType) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(contract_type) -> decode.success(contract_type) + Error(Nil) -> decode.failure(Procurement, "ContractType") + } +} + +pub fn to_string(contract_type: ContractType) -> String { + case contract_type { + Procurement -> "PROCUREMENT" + Transport -> "TRANSPORT" + Shuttle -> "SHUTTLE" + } +} + +pub fn encode(contract_type: ContractType) -> Json { + json.string(to_string(contract_type)) +} diff --git a/src/spacetraders_models/cooldown.gleam b/src/spacetraders_models/cooldown.gleam new file mode 100644 index 0000000..85793f6 --- /dev/null +++ b/src/spacetraders_models/cooldown.gleam @@ -0,0 +1,31 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/internal/time +import spacetraders_models/ship_symbol.{type ShipSymbol} + +pub type Cooldown { + Cooldown( + ship_symbol: ShipSymbol, + total_seconds: Int, + remaining_seconds: Int, + expiration: Option(Timestamp), + ) +} + +pub fn decoder() -> Decoder(Cooldown) { + use ship_symbol <- decode.field("shipSymbol", ship_symbol.decoder()) + use total_seconds <- decode.field("totalSeconds", decode.int) + use remaining_seconds <- decode.field("remainingSeconds", decode.int) + use expiration <- decode.optional_field( + "expiration", + option.None, + decode.optional(time.rfc3339_timestamp_decoder()), + ) + decode.success(Cooldown( + ship_symbol:, + total_seconds:, + remaining_seconds:, + expiration:, + )) +} diff --git a/src/spacetraders_models/crew_rotation.gleam b/src/spacetraders_models/crew_rotation.gleam new file mode 100644 index 0000000..963b8c5 --- /dev/null +++ b/src/spacetraders_models/crew_rotation.gleam @@ -0,0 +1,34 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type CrewRotation { + Strict + Relaxed +} + +pub fn parse(value: String) -> Result(CrewRotation, Nil) { + case value { + "STRICT" -> Ok(Strict) + "RELAXED" -> Ok(Relaxed) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(CrewRotation) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(crew_rotation) -> decode.success(crew_rotation) + Error(Nil) -> decode.failure(Strict, "CrewRotation") + } +} + +pub fn to_string(crew_rotation: CrewRotation) -> String { + case crew_rotation { + Strict -> "STRICT" + Relaxed -> "RELAXED" + } +} + +pub fn encode(crew_rotation: CrewRotation) -> Json { + json.string(to_string(crew_rotation)) +} diff --git a/src/spacetraders_models/engine_symbol.gleam b/src/spacetraders_models/engine_symbol.gleam new file mode 100644 index 0000000..1441a0f --- /dev/null +++ b/src/spacetraders_models/engine_symbol.gleam @@ -0,0 +1,40 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type EngineSymbol { + EngineImpulseDriveI + EngineIonDriveI + EngineIonDriveII + EngineHyperDriveI +} + +pub fn parse(value: String) -> Result(EngineSymbol, Nil) { + case value { + "ENGINE_IMPULSE_DRIVE_I" -> Ok(EngineImpulseDriveI) + "ENGINE_ION_DRIVE_I" -> Ok(EngineIonDriveI) + "ENGINE_ION_DRIVE_II" -> Ok(EngineIonDriveII) + "ENGINE_HYPER_DRIVE_I" -> Ok(EngineHyperDriveI) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(EngineSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(engine_symbol) -> decode.success(engine_symbol) + Error(Nil) -> decode.failure(EngineImpulseDriveI, "EngineSymbol") + } +} + +pub fn to_string(engine_symbol: EngineSymbol) -> String { + case engine_symbol { + EngineImpulseDriveI -> "ENGINE_IMPULSE_DRIVE_I" + EngineIonDriveI -> "ENGINE_ION_DRIVE_I" + EngineIonDriveII -> "ENGINE_ION_DRIVE_II" + EngineHyperDriveI -> "ENGINE_HYPER_DRIVE_I" + } +} + +pub fn encode(engine_symbol: EngineSymbol) -> Json { + json.string(to_string(engine_symbol)) +} diff --git a/src/spacetraders_models/extraction.gleam b/src/spacetraders_models/extraction.gleam new file mode 100644 index 0000000..793ba23 --- /dev/null +++ b/src/spacetraders_models/extraction.gleam @@ -0,0 +1,13 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/extraction_yield.{type ExtractionYield} +import spacetraders_models/ship_symbol.{type ShipSymbol} + +pub type Extraction { + Extraction(ship_symbol: ShipSymbol, yield: ExtractionYield) +} + +pub fn decoder() -> Decoder(Extraction) { + use ship_symbol <- decode.field("shipSymbol", ship_symbol.decoder()) + use yield <- decode.field("yield", extraction_yield.decoder()) + decode.success(Extraction(ship_symbol:, yield:)) +} diff --git a/src/spacetraders_models/extraction_yield.gleam b/src/spacetraders_models/extraction_yield.gleam new file mode 100644 index 0000000..0b84e1a --- /dev/null +++ b/src/spacetraders_models/extraction_yield.gleam @@ -0,0 +1,12 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/trade_symbol.{type TradeSymbol} + +pub type ExtractionYield { + ExtractionYield(symbol: TradeSymbol, units: Int) +} + +pub fn decoder() -> Decoder(ExtractionYield) { + use symbol <- decode.field("symbol", trade_symbol.decoder()) + use units <- decode.field("units", decode.int) + decode.success(ExtractionYield(symbol:, units:)) +} diff --git a/src/spacetraders_models/faction.gleam b/src/spacetraders_models/faction.gleam new file mode 100644 index 0000000..b2f2724 --- /dev/null +++ b/src/spacetraders_models/faction.gleam @@ -0,0 +1,37 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/faction_symbol.{type FactionSymbol} +import spacetraders_models/faction_trait.{type FactionTrait} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type Faction { + Faction( + symbol: FactionSymbol, + name: String, + description: String, + headquarters: Option(WaypointSymbol), + traits: List(FactionTrait), + is_recruiting: Bool, + ) +} + +pub fn decoder() -> Decoder(Faction) { + use symbol <- decode.field("symbol", faction_symbol.decoder()) + use name <- decode.field("name", decode.string) + use description <- decode.field("description", decode.string) + use headquarters <- decode.optional_field( + "headquarters", + option.None, + decode.optional(waypoint_symbol.decoder()), + ) + use traits <- decode.field("traits", decode.list(faction_trait.decoder())) + use is_recruiting <- decode.field("isRecruiting", decode.bool) + decode.success(Faction( + symbol:, + name:, + description:, + headquarters:, + traits:, + is_recruiting:, + )) +} diff --git a/src/spacetraders_models/faction_symbol.gleam b/src/spacetraders_models/faction_symbol.gleam new file mode 100644 index 0000000..583de57 --- /dev/null +++ b/src/spacetraders_models/faction_symbol.gleam @@ -0,0 +1,85 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type FactionSymbol { + Cosmic + Void + Galactic + Quantum + Dominion + Astro + Corsairs + Obsidian + Aegis + United + Solitary + Cobalt + Omega + Echo + Lord + Cult + Ancients + Shadow + Ethereal +} + +pub fn parse(value: String) -> Result(FactionSymbol, Nil) { + case value { + "COSMIC" -> Ok(Cosmic) + "VOID" -> Ok(Void) + "GALACTIC" -> Ok(Galactic) + "QUANTUM" -> Ok(Quantum) + "DOMINION" -> Ok(Dominion) + "ASTRO" -> Ok(Astro) + "CORSAIRS" -> Ok(Corsairs) + "OBSIDIAN" -> Ok(Obsidian) + "AEGIS" -> Ok(Aegis) + "UNITED" -> Ok(United) + "SOLITARY" -> Ok(Solitary) + "COBALT" -> Ok(Cobalt) + "OMEGA" -> Ok(Omega) + "ECHO" -> Ok(Echo) + "LORDS" -> Ok(Lord) + "CULT" -> Ok(Cult) + "ANCIENTS" -> Ok(Ancients) + "SHADOW" -> Ok(Shadow) + "ETHEREAL" -> Ok(Ethereal) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(FactionSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(faction_symbol) -> decode.success(faction_symbol) + Error(Nil) -> decode.failure(Cosmic, "FactionSymbol") + } +} + +pub fn to_string(faction_symbol: FactionSymbol) -> String { + case faction_symbol { + Cosmic -> "COSMIC" + Void -> "VOID" + Galactic -> "GALACTIC" + Quantum -> "QUANTUM" + Dominion -> "DOMINION" + Astro -> "ASTRO" + Corsairs -> "CORSAIRS" + Obsidian -> "OBSIDIAN" + Aegis -> "AEGIS" + United -> "UNITED" + Solitary -> "SOLITARY" + Cobalt -> "COBALT" + Omega -> "OMEGA" + Echo -> "ECHO" + Lord -> "LORDS" + Cult -> "CULT" + Ancients -> "ANCIENTS" + Shadow -> "SHADOW" + Ethereal -> "ETHEREAL" + } +} + +pub fn encode(faction_symbol: FactionSymbol) -> Json { + json.string(to_string(faction_symbol)) +} diff --git a/src/spacetraders_models/faction_trait.gleam b/src/spacetraders_models/faction_trait.gleam new file mode 100644 index 0000000..3bbc343 --- /dev/null +++ b/src/spacetraders_models/faction_trait.gleam @@ -0,0 +1,13 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/faction_trait_symbol.{type FactionTraitSymbol} + +pub type FactionTrait { + FactionTrait(symbol: FactionTraitSymbol, name: String, description: String) +} + +pub fn decoder() -> Decoder(FactionTrait) { + use symbol <- decode.field("symbol", faction_trait_symbol.decoder()) + use name <- decode.field("name", decode.string) + use description <- decode.field("description", decode.string) + decode.success(FactionTrait(symbol:, name:, description:)) +} diff --git a/src/spacetraders_models/faction_trait_symbol.gleam b/src/spacetraders_models/faction_trait_symbol.gleam new file mode 100644 index 0000000..0bdfbf0 --- /dev/null +++ b/src/spacetraders_models/faction_trait_symbol.gleam @@ -0,0 +1,205 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type FactionTraitSymbol { + Bureaucratic + Secretive + Capitalistic + Industrious + Peaceful + Distrustful + Welcoming + Smugglers + Scavengers + Rebellious + Exiles + Pirates + Raiders + Clan + Guild + Dominion + Fringe + Forsaken + Isolated + Localized + Established + Notable + Dominant + Inescapable + Innovative + Bold + Visionary + Curious + Daring + Exploratory + Resourceful + Flexible + Cooperative + United + Strategic + Intelligent + ResearchFocused + Collaborative + Progressive + Militaristic + TechnologicallyAdvanced + Aggressive + Imperialistic + TreasureHunters + Dexterous + Unpredictable + Brutal + Fleeting + Adaptable + SelfSufficient + Defensive + Proud + Diverse + Independent + SelfInterested + Fragmented + Commercial + FreeMarkets + Entrepreneurial +} + +pub fn parse(value: String) -> Result(FactionTraitSymbol, Nil) { + case value { + "BUREAUCRATIC" -> Ok(Bureaucratic) + "SECRETIVE" -> Ok(Secretive) + "CAPITALISTIC" -> Ok(Capitalistic) + "INDUSTRIOUS" -> Ok(Industrious) + "PEACEFUL" -> Ok(Peaceful) + "DISTRUSTFUL" -> Ok(Distrustful) + "WELCOMING" -> Ok(Welcoming) + "SMUGGLERS" -> Ok(Smugglers) + "SCAVENGERS" -> Ok(Scavengers) + "REBELLIOUS" -> Ok(Rebellious) + "EXILES" -> Ok(Exiles) + "PIRATES" -> Ok(Pirates) + "RAIDERS" -> Ok(Raiders) + "CLAN" -> Ok(Clan) + "GUILD" -> Ok(Guild) + "DOMINION" -> Ok(Dominion) + "FRINGE" -> Ok(Fringe) + "FORSAKEN" -> Ok(Forsaken) + "ISOLATED" -> Ok(Isolated) + "LOCALIZED" -> Ok(Localized) + "ESTABLISHED" -> Ok(Established) + "NOTABLE" -> Ok(Notable) + "DOMINANT" -> Ok(Dominant) + "INESCAPABLE" -> Ok(Inescapable) + "INNOVATIVE" -> Ok(Innovative) + "BOLD" -> Ok(Bold) + "VISIONARY" -> Ok(Visionary) + "CURIOUS" -> Ok(Curious) + "DARING" -> Ok(Daring) + "EXPLORATORY" -> Ok(Exploratory) + "RESOURCEFUL" -> Ok(Resourceful) + "FLEXIBLE" -> Ok(Flexible) + "COOPERATIVE" -> Ok(Cooperative) + "UNITED" -> Ok(United) + "STRATEGIC" -> Ok(Strategic) + "INTELLIGENT" -> Ok(Intelligent) + "RESEARCH_FOCUSED" -> Ok(ResearchFocused) + "COLLABORATIVE" -> Ok(Collaborative) + "PROGRESSIVE" -> Ok(Progressive) + "MILITARISTIC" -> Ok(Militaristic) + "TECHNOLOGICALLY_ADVANCED" -> Ok(TechnologicallyAdvanced) + "AGGRESSIVE" -> Ok(Aggressive) + "IMPERIALISTIC" -> Ok(Imperialistic) + "TREASURE_HUNTERS" -> Ok(TreasureHunters) + "DEXTEROUS" -> Ok(Dexterous) + "UNPREDICTABLE" -> Ok(Unpredictable) + "BRUTAL" -> Ok(Brutal) + "FLEETING" -> Ok(Fleeting) + "ADAPTABLE" -> Ok(Adaptable) + "SELF_SUFFICIENT" -> Ok(SelfSufficient) + "DEFENSIVE" -> Ok(Defensive) + "PROUD" -> Ok(Proud) + "DIVERSE" -> Ok(Diverse) + "INDEPENDENT" -> Ok(Independent) + "SELF_INTERESTED" -> Ok(SelfInterested) + "FRAGMENTED" -> Ok(Fragmented) + "COMMERCIAL" -> Ok(Commercial) + "FREE_MARKETS" -> Ok(FreeMarkets) + "ENTREPRENEURIAL" -> Ok(Entrepreneurial) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(FactionTraitSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(faction_trait_symbol) -> decode.success(faction_trait_symbol) + Error(Nil) -> decode.failure(Bureaucratic, "FactionTraitSymbol") + } +} + +pub fn to_string(faction_trait_symbol: FactionTraitSymbol) -> String { + case faction_trait_symbol { + Bureaucratic -> "BUREAUCRATIC" + Secretive -> "SECRETIVE" + Capitalistic -> "CAPITALISTIC" + Industrious -> "INDUSTRIOUS" + Peaceful -> "PEACEFUL" + Distrustful -> "DISTRUSTFUL" + Welcoming -> "WELCOMING" + Smugglers -> "SMUGGLERS" + Scavengers -> "SCAVENGERS" + Rebellious -> "REBELLIOUS" + Exiles -> "EXILES" + Pirates -> "PIRATES" + Raiders -> "RAIDERS" + Clan -> "CLAN" + Guild -> "GUILD" + Dominion -> "DOMINION" + Fringe -> "FRINGE" + Forsaken -> "FORSAKEN" + Isolated -> "ISOLATED" + Localized -> "LOCALIZED" + Established -> "ESTABLISHED" + Notable -> "NOTABLE" + Dominant -> "DOMINANT" + Inescapable -> "INESCAPABLE" + Innovative -> "INNOVATIVE" + Bold -> "BOLD" + Visionary -> "VISIONARY" + Curious -> "CURIOUS" + Daring -> "DARING" + Exploratory -> "EXPLORATORY" + Resourceful -> "RESOURCEFUL" + Flexible -> "FLEXIBLE" + Cooperative -> "COOPERATIVE" + United -> "UNITED" + Strategic -> "STRATEGIC" + Intelligent -> "INTELLIGENT" + ResearchFocused -> "RESEARCH_FOCUSED" + Collaborative -> "COLLABORATIVE" + Progressive -> "PROGRESSIVE" + Militaristic -> "MILITARISTIC" + TechnologicallyAdvanced -> "TECHNOLOGICALLY_ADVANCED" + Aggressive -> "AGGRESSIVE" + Imperialistic -> "IMPERIALISTIC" + TreasureHunters -> "TREASURE_HUNTERS" + Dexterous -> "DEXTEROUS" + Unpredictable -> "UNPREDICTABLE" + Brutal -> "BRUTAL" + Fleeting -> "FLEETING" + Adaptable -> "ADAPTABLE" + SelfSufficient -> "SELF_SUFFICIENT" + Defensive -> "DEFENSIVE" + Proud -> "PROUD" + Diverse -> "DIVERSE" + Independent -> "INDEPENDENT" + SelfInterested -> "SELF_INTERESTED" + Fragmented -> "FRAGMENTED" + Commercial -> "COMMERCIAL" + FreeMarkets -> "FREE_MARKETS" + Entrepreneurial -> "ENTREPRENEURIAL" + } +} + +pub fn encode(faction_trait_symbol: FactionTraitSymbol) -> Json { + json.string(to_string(faction_trait_symbol)) +} diff --git a/src/spacetraders_models/frame_symbol.gleam b/src/spacetraders_models/frame_symbol.gleam new file mode 100644 index 0000000..25332c8 --- /dev/null +++ b/src/spacetraders_models/frame_symbol.gleam @@ -0,0 +1,76 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type FrameSymbol { + FrameProbe + FrameDrone + FrameInterceptor + FrameRacer + FrameFighter + FrameFrigate + FrameShuttle + FrameExplorer + FrameMiner + FrameLightFreighter + FrameHeavyFreighter + FrameTransport + FrameDestroyer + FrameCruiser + FrameCarrier + FrameBulkFreighter +} + +pub fn parse(value: String) -> Result(FrameSymbol, Nil) { + case value { + "FRAME_PROBE" -> Ok(FrameProbe) + "FRAME_DRONE" -> Ok(FrameDrone) + "FRAME_INTERCEPTOR" -> Ok(FrameInterceptor) + "FRAME_RACER" -> Ok(FrameRacer) + "FRAME_FIGHTER" -> Ok(FrameFighter) + "FRAME_FRIGATE" -> Ok(FrameFrigate) + "FRAME_SHUTTLE" -> Ok(FrameShuttle) + "FRAME_EXPLORER" -> Ok(FrameExplorer) + "FRAME_MINER" -> Ok(FrameMiner) + "FRAME_LIGHT_FREIGHTER" -> Ok(FrameLightFreighter) + "FRAME_HEAVY_FREIGHTER" -> Ok(FrameHeavyFreighter) + "FRAME_TRANSPORT" -> Ok(FrameTransport) + "FRAME_DESTROYER" -> Ok(FrameDestroyer) + "FRAME_CRUISER" -> Ok(FrameCruiser) + "FRAME_CARRIER" -> Ok(FrameCarrier) + "FRAME_BULK_FREIGHTER" -> Ok(FrameBulkFreighter) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(FrameSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(frame_symbol) -> decode.success(frame_symbol) + Error(Nil) -> decode.failure(FrameProbe, "FrameSymbol") + } +} + +pub fn to_string(frame_symbol: FrameSymbol) -> String { + case frame_symbol { + FrameProbe -> "FRAME_PROBE" + FrameDrone -> "FRAME_DRONE" + FrameInterceptor -> "FRAME_INTERCEPTOR" + FrameRacer -> "FRAME_RACER" + FrameFighter -> "FRAME_FIGHTER" + FrameFrigate -> "FRAME_FRIGATE" + FrameShuttle -> "FRAME_SHUTTLE" + FrameExplorer -> "FRAME_EXPLORER" + FrameMiner -> "FRAME_MINER" + FrameLightFreighter -> "FRAME_LIGHT_FREIGHTER" + FrameHeavyFreighter -> "FRAME_HEAVY_FREIGHTER" + FrameTransport -> "FRAME_TRANSPORT" + FrameDestroyer -> "FRAME_DESTROYER" + FrameCruiser -> "FRAME_CRUISER" + FrameCarrier -> "FRAME_CARRIER" + FrameBulkFreighter -> "FRAME_BULK_FREIGHTER" + } +} + +pub fn encode(frame_symbol: FrameSymbol) -> Json { + json.string(to_string(frame_symbol)) +} diff --git a/src/spacetraders_models/internal/jwt.gleam b/src/spacetraders_models/internal/jwt.gleam new file mode 100644 index 0000000..aba9d1e --- /dev/null +++ b/src/spacetraders_models/internal/jwt.gleam @@ -0,0 +1,143 @@ +import gleam/bit_array +import gleam/dynamic/decode.{type Decoder} +import gleam/json +import gleam/option.{type Option} +import gleam/result +import gleam/string +import gleam/time/calendar.{type Date} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/internal/time + +pub type JwtDecodeError { + MissingHeader + MissingPayload + MissingSignature + InvalidHeader + InvalidPayload + InvalidSignature + InvalidExpiration + TokenExpired + TokenNotValidYet + InvalidNotBefore + InvalidAlgorithm +} + +pub type JwtAlgorithm { + HS256 + HS384 + HS512 + RS256 + RS384 + RS512 + ES256 + ES384 + ES512 + PS256 + PS384 + PS512 + None +} + +pub fn parse_jwt_algorithm(value: String) -> Result(JwtAlgorithm, Nil) { + case value { + "HS256" -> Ok(HS256) + "HS384" -> Ok(HS384) + "HS512" -> Ok(HS512) + "RS256" -> Ok(RS256) + "RS384" -> Ok(RS384) + "RS512" -> Ok(RS512) + "ES256" -> Ok(ES256) + "ES384" -> Ok(ES384) + "ES512" -> Ok(ES512) + "PS256" -> Ok(PS256) + "PS384" -> Ok(PS384) + "PS512" -> Ok(PS512) + "none" -> Ok(None) + _ -> Error(Nil) + } +} + +pub fn jwt_algorithm_decoder() -> Decoder(JwtAlgorithm) { + use value <- decode.then(decode.string) + case parse_jwt_algorithm(value) { + Ok(jwt_algorithm) -> decode.success(jwt_algorithm) + Error(Nil) -> decode.failure(None, "JwtAlgorithm") + } +} + +pub type JwtHeader { + JwtHeader(algorithm: JwtAlgorithm, token_type: String) +} + +fn jwt_header_decoder() -> Decoder(JwtHeader) { + use algorithm <- decode.field("alg", jwt_algorithm_decoder()) + use token_type <- decode.field("typ", decode.string) + decode.success(JwtHeader(algorithm:, token_type:)) +} + +pub type JwtPayload { + JwtPayload( + identifier: String, + version: String, + reset_date: Option(Date), + issued_at: Timestamp, + subject: String, + ) +} + +fn jwt_payload_decoder() -> Decoder(JwtPayload) { + use identifier <- decode.field("identifier", decode.string) + use version <- decode.field("version", decode.string) + use reset_date <- decode.optional_field( + "reset_date", + option.None, + decode.optional(time.iso8601_date_decoder()), + ) + use issued_at_int <- decode.field("iat", decode.int) + let issued_at = timestamp.from_unix_seconds(issued_at_int) + use subject <- decode.field("sub", decode.string) + decode.success(JwtPayload( + identifier:, + version:, + reset_date:, + issued_at:, + subject:, + )) +} + +pub type Jwt { + Jwt(header: JwtHeader, payload: JwtPayload, signature: String) +} + +pub fn parse(token: String) -> Result(Jwt, JwtDecodeError) { + case string.split(token, ".") { + [encoded_header, encoded_payload, signature, ..] -> { + use header <- result.try( + bit_array.base64_url_decode(encoded_header) + |> result.replace_error(InvalidHeader) + |> result.try(fn(x) { + bit_array.to_string(x) |> result.replace_error(InvalidHeader) + }) + |> result.try(fn(x) { + json.parse(x, jwt_header_decoder()) + |> result.replace_error(InvalidHeader) + }), + ) + use payload <- result.try( + bit_array.base64_url_decode(encoded_payload) + |> result.replace_error(InvalidPayload) + |> result.try(fn(x) { + bit_array.to_string(x) |> result.replace_error(InvalidPayload) + }) + |> result.try(fn(x) { + json.parse(x, jwt_payload_decoder()) + |> result.replace_error(InvalidPayload) + }), + ) + Ok(Jwt(header:, payload:, signature:)) + } + [_, _] -> Error(MissingSignature) + [_] -> Error(MissingPayload) + [] -> Error(MissingHeader) + } +} diff --git a/src/spacetraders_models/internal/time.gleam b/src/spacetraders_models/internal/time.gleam new file mode 100644 index 0000000..6431bb3 --- /dev/null +++ b/src/spacetraders_models/internal/time.gleam @@ -0,0 +1,219 @@ +import gleam/bit_array +import gleam/dynamic/decode.{type Decoder} +import gleam/pair +import gleam/result +import gleam/time/calendar.{ + type Date, type Month, type TimeOfDay, April, August, Date, December, February, + January, July, June, March, May, November, October, September, TimeOfDay, +} +import gleam/time/timestamp.{type Timestamp} + +pub fn rfc3339_timestamp_decoder() -> Decoder(Timestamp) { + use value <- decode.then(decode.string) + case timestamp.parse_rfc3339(value) { + Ok(timestamp) -> decode.success(timestamp) + Error(Nil) -> decode.failure(timestamp.from_unix_seconds(0), "Timestamp") + } +} + +pub fn parse_iso8601_date(input: String) -> Result(Date, Nil) { + let bytes = bit_array.from_string(input) + use #(year, bytes) <- result.try(parse_year(from: bytes)) + use bytes <- result.try(accept_byte(from: bytes, value: byte_minus)) + use #(month, bytes) <- result.try(parse_month_calendar(from: bytes)) + use bytes <- result.try(accept_byte(from: bytes, value: byte_minus)) + use #(day, bytes) <- result.try(parse_day_calendar(from: bytes, year:, month:)) + use Nil <- result.try(accept_empty(bytes)) + Ok(Date(year:, month:, day:)) +} + +pub fn iso8601_date_decoder() -> Decoder(Date) { + use value <- decode.then(decode.string) + case parse_iso8601_date(value) { + Ok(date) -> decode.success(date) + Error(Nil) -> decode.failure(Date(1970, January, 1), "Date") + } +} + +pub fn parse_iso8601_time_of_day(input: String) -> Result(TimeOfDay, Nil) { + let bytes = bit_array.from_string(input) + use #(hours, bytes) <- result.try(parse_hours(from: bytes)) + use bytes <- result.try(accept_byte(from: bytes, value: byte_colon)) + use #(minutes, bytes) <- result.try(parse_minutes(from: bytes)) + use bytes <- result.try(accept_byte(from: bytes, value: byte_colon)) + use #(seconds, bytes) <- result.try(parse_seconds(from: bytes)) + use #(nanoseconds, bytes) <- result.try(parse_second_fraction_as_nanoseconds( + from: bytes, + )) + use Nil <- result.try(accept_empty(bytes)) + Ok(TimeOfDay(hours:, minutes:, seconds:, nanoseconds:)) +} + +pub fn iso8601_time_of_day_decoder() -> Decoder(TimeOfDay) { + use value <- decode.then(decode.string) + case parse_iso8601_time_of_day(value) { + Ok(date) -> decode.success(date) + Error(Nil) -> decode.failure(TimeOfDay(0, 0, 0, 0), "TimeOfDay") + } +} + +const byte_zero: Int = 0x30 + +const byte_nine: Int = 0x39 + +const byte_colon: Int = 0x3A + +const byte_minus: Int = 0x2D + +const nanoseconds_per_second: Int = 1_000_000_000 + +fn accept_byte(from bytes: BitArray, value value: Int) -> Result(BitArray, Nil) { + case bytes { + <> if byte == value -> Ok(remaining_bytes) + _ -> Error(Nil) + } +} + +fn accept_empty(from bytes: BitArray) -> Result(Nil, Nil) { + case bytes { + <<>> -> Ok(Nil) + _ -> Error(Nil) + } +} + +fn parse_digits( + from bytes: BitArray, + count count: Int, +) -> Result(#(Int, BitArray), Nil) { + do_parse_digits(from: bytes, count:, acc: 0, k: 0) +} + +fn do_parse_digits( + from bytes: BitArray, + count count: Int, + acc acc: Int, + k k: Int, +) -> Result(#(Int, BitArray), Nil) { + case bytes { + _ if k >= count -> Ok(#(acc, bytes)) + <> if byte_zero <= byte && byte <= byte_nine -> + do_parse_digits( + from: remaining_bytes, + count:, + acc: acc * 10 + { byte - 0x30 }, + k: k + 1, + ) + _ -> Error(Nil) + } +} + +fn parse_year(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + parse_digits(from: bytes, count: 4) +} + +// slightly modified version of parse_month that returns calendar.Month instead of Int +fn parse_month_calendar(from bytes: BitArray) -> Result(#(Month, BitArray), Nil) { + use #(month, bytes) <- result.try(parse_digits(from: bytes, count: 2)) + calendar.month_from_int(month) |> result.map(pair.new(_, bytes)) +} + +// slightly modified version of parse_day that takes calendar.Month instead of Int +fn parse_day_calendar( + from bytes: BitArray, + year year: Int, + month month: Month, +) -> Result(#(Int, BitArray), Nil) { + use #(day, bytes) <- result.try(parse_digits(from: bytes, count: 2)) + let max_day = case month { + January | March | May | July | August | October | December -> 31 + April | June | September | November -> 30 + February -> { + case is_leap_year(year) { + True -> 29 + False -> 28 + } + } + } + case 1 <= day && day <= max_day { + True -> Ok(#(day, bytes)) + False -> Error(Nil) + } +} + +fn is_leap_year(year: Int) -> Bool { + year % 4 == 0 && { year % 100 != 0 || year % 400 == 0 } +} + +fn parse_hours(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + use #(hours, bytes) <- result.try(parse_digits(from: bytes, count: 2)) + case 0 <= hours && hours <= 23 { + True -> Ok(#(hours, bytes)) + False -> Error(Nil) + } +} + +fn parse_minutes(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + use #(minutes, bytes) <- result.try(parse_digits(from: bytes, count: 2)) + case 0 <= minutes && minutes <= 59 { + True -> Ok(#(minutes, bytes)) + False -> Error(Nil) + } +} + +fn parse_seconds(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + use #(seconds, bytes) <- result.try(parse_digits(from: bytes, count: 2)) + case 0 <= seconds && seconds <= 60 { + True -> Ok(#(seconds, bytes)) + False -> Error(Nil) + } +} + +fn parse_second_fraction_as_nanoseconds(from bytes: BitArray) { + case bytes { + <<".", byte, remaining_bytes:bytes>> + if byte_zero <= byte && byte <= byte_nine + -> { + do_parse_second_fraction_as_nanoseconds( + from: <>, + acc: 0, + power: nanoseconds_per_second, + ) + } + <<".", _:bytes>> -> Error(Nil) + _ -> Ok(#(0, bytes)) + } +} + +fn do_parse_second_fraction_as_nanoseconds( + from bytes: BitArray, + acc acc: Int, + power power: Int, +) -> Result(#(Int, BitArray), a) { + // Each digit place to the left in the fractional second is 10x fewer + // nanoseconds. + let power = power / 10 + + case bytes { + <> + if byte_zero <= byte && byte <= byte_nine && power < 1 + -> { + // We already have the max precision for nanoseconds. Truncate any + // remaining digits. + do_parse_second_fraction_as_nanoseconds( + from: remaining_bytes, + acc:, + power:, + ) + } + <> if byte_zero <= byte && byte <= byte_nine -> { + // We have not yet reached the precision limit. Parse the next digit. + let digit = byte - 0x30 + do_parse_second_fraction_as_nanoseconds( + from: remaining_bytes, + acc: acc + digit * power, + power:, + ) + } + _ -> Ok(#(acc, bytes)) + } +} diff --git a/src/spacetraders_models/jump_gate.gleam b/src/spacetraders_models/jump_gate.gleam new file mode 100644 index 0000000..203f2f6 --- /dev/null +++ b/src/spacetraders_models/jump_gate.gleam @@ -0,0 +1,15 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type JumpGate { + JumpGate(symbol: WaypointSymbol, connections: List(WaypointSymbol)) +} + +pub fn decoder() -> Decoder(JumpGate) { + use symbol <- decode.field("symbol", waypoint_symbol.decoder()) + use connections <- decode.field( + "connections", + decode.list(waypoint_symbol.decoder()), + ) + decode.success(JumpGate(symbol:, connections:)) +} diff --git a/src/spacetraders_models/market.gleam b/src/spacetraders_models/market.gleam new file mode 100644 index 0000000..382d4ab --- /dev/null +++ b/src/spacetraders_models/market.gleam @@ -0,0 +1,42 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/market_symbol.{type MarketSymbol} +import spacetraders_models/market_trade_good.{type MarketTradeGood} +import spacetraders_models/market_transaction.{type MarketTransaction} +import spacetraders_models/trade_good.{type TradeGood} + +pub type Market { + Market( + symbol: MarketSymbol, + exports: List(TradeGood), + imports: List(TradeGood), + exchange: List(TradeGood), + transactions: Option(List(MarketTransaction)), + trade_goods: Option(List(MarketTradeGood)), + ) +} + +pub fn decoder() -> Decoder(Market) { + use symbol <- decode.field("symbol", market_symbol.decoder()) + use exports <- decode.field("exports", decode.list(trade_good.decoder())) + use imports <- decode.field("imports", decode.list(trade_good.decoder())) + use exchange <- decode.field("exchange", decode.list(trade_good.decoder())) + use transactions <- decode.optional_field( + "transactions", + option.None, + decode.optional(decode.list(market_transaction.decoder())), + ) + use trade_goods <- decode.optional_field( + "tradeGoods", + option.None, + decode.optional(decode.list(market_trade_good.decoder())), + ) + decode.success(Market( + symbol:, + exports:, + imports:, + exchange:, + transactions:, + trade_goods:, + )) +} diff --git a/src/spacetraders_models/market_symbol.gleam b/src/spacetraders_models/market_symbol.gleam new file mode 100644 index 0000000..cc437d8 --- /dev/null +++ b/src/spacetraders_models/market_symbol.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type MarketSymbol { + MarketSymbol(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(MarketSymbol, Nil) { + case string.length(value) >= min_length { + True -> Ok(MarketSymbol(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(MarketSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(market_symbol) -> decode.success(market_symbol) + Error(Nil) -> decode.failure(MarketSymbol("invalid"), "MarketSymbol") + } +} + +pub fn to_string(market_symbol: MarketSymbol) -> String { + let MarketSymbol(symbol) = market_symbol + symbol +} + +pub fn encode(market_symbol: MarketSymbol) -> Json { + json.string(to_string(market_symbol)) +} diff --git a/src/spacetraders_models/market_trade_good.gleam b/src/spacetraders_models/market_trade_good.gleam new file mode 100644 index 0000000..78d3dea --- /dev/null +++ b/src/spacetraders_models/market_trade_good.gleam @@ -0,0 +1,41 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/activity_level.{type ActivityLevel} +import spacetraders_models/supply_level.{type SupplyLevel} +import spacetraders_models/trade_good_type.{type TradeGoodType} +import spacetraders_models/trade_symbol.{type TradeSymbol} + +pub type MarketTradeGood { + MarketTradeGood( + symbol: TradeSymbol, + type_: TradeGoodType, + trade_volume: Int, + supply: SupplyLevel, + activity: Option(ActivityLevel), + purchase_price: Int, + sell_price: Int, + ) +} + +pub fn decoder() -> Decoder(MarketTradeGood) { + use symbol <- decode.field("symbol", trade_symbol.decoder()) + use type_ <- decode.field("type", trade_good_type.decoder()) + use trade_volume <- decode.field("tradeVolume", decode.int) + use supply <- decode.field("supply", supply_level.decoder()) + use activity <- decode.optional_field( + "activity", + option.None, + decode.optional(activity_level.decoder()), + ) + use purchase_price <- decode.field("purchasePrice", decode.int) + use sell_price <- decode.field("sellPrice", decode.int) + decode.success(MarketTradeGood( + symbol:, + type_:, + trade_volume:, + supply:, + activity:, + purchase_price:, + sell_price:, + )) +} diff --git a/src/spacetraders_models/market_transaction.gleam b/src/spacetraders_models/market_transaction.gleam new file mode 100644 index 0000000..aad9510 --- /dev/null +++ b/src/spacetraders_models/market_transaction.gleam @@ -0,0 +1,44 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/internal/time +import spacetraders_models/ship_symbol.{type ShipSymbol} +import spacetraders_models/trade_symbol.{type TradeSymbol} +import spacetraders_models/transaction_type.{type TransactionType} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type MarketTransaction { + MarketTransaction( + waypoint_symbol: WaypointSymbol, + ship_symbol: ShipSymbol, + trade_symbol: TradeSymbol, + type_: TransactionType, + units: Int, + price_per_unit: Int, + total_price: Int, + timestamp: Timestamp, + ) +} + +pub fn decoder() -> Decoder(MarketTransaction) { + use waypoint_symbol <- decode.field( + "waypointSymbol", + waypoint_symbol.decoder(), + ) + use ship_symbol <- decode.field("shipSymbol", ship_symbol.decoder()) + use trade_symbol <- decode.field("tradeSymbol", trade_symbol.decoder()) + use type_ <- decode.field("type", transaction_type.decoder()) + use units <- decode.field("units", decode.int) + use price_per_unit <- decode.field("pricePerUnit", decode.int) + use total_price <- decode.field("totalPrice", decode.int) + use timestamp <- decode.field("timestamp", time.rfc3339_timestamp_decoder()) + decode.success(MarketTransaction( + waypoint_symbol:, + ship_symbol:, + trade_symbol:, + type_:, + units:, + price_per_unit:, + total_price:, + timestamp:, + )) +} diff --git a/src/spacetraders_models/meta.gleam b/src/spacetraders_models/meta.gleam new file mode 100644 index 0000000..9c00ca7 --- /dev/null +++ b/src/spacetraders_models/meta.gleam @@ -0,0 +1,12 @@ +import gleam/dynamic/decode.{type Decoder} + +pub type Meta { + Meta(total: Int, page: Int, limit: Int) +} + +pub fn decoder() -> Decoder(Meta) { + use total <- decode.field("total", decode.int) + use page <- decode.field("page", decode.int) + use limit <- decode.field("limit", decode.int) + decode.success(Meta(total:, page:, limit:)) +} diff --git a/src/spacetraders_models/module_symbol.gleam b/src/spacetraders_models/module_symbol.gleam new file mode 100644 index 0000000..e51cc0b --- /dev/null +++ b/src/spacetraders_models/module_symbol.gleam @@ -0,0 +1,88 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type ModuleSymbol { + ModuleMineralProcessorI + ModuleGasProcessorI + ModuleCargoHoldI + ModuleCargoHoldII + ModuleCargoHoldIII + ModuleCrewQuartersI + ModuleEnvoyQuartersI + ModulePassengerCabinI + ModuleMicroRefineryI + ModuleOreRefineryI + ModuleFuelRefineryI + ModuleScienceLabI + ModuleJumpDriveI + ModuleJumpDriveII + ModuleJumpDriveIII + ModuleWarpDriveI + ModuleWarpDriveII + ModuleWarpDriveIII + ModuleShieldGeneratorI + ModuleShieldGeneratorII +} + +pub fn parse(value: String) -> Result(ModuleSymbol, Nil) { + case value { + "MODULE_MINERAL_PROCESSOR_I" -> Ok(ModuleMineralProcessorI) + "MODULE_GAS_PROCESSOR_I" -> Ok(ModuleGasProcessorI) + "MODULE_CARGO_HOLD_I" -> Ok(ModuleCargoHoldI) + "MODULE_CARGO_HOLD_II" -> Ok(ModuleCargoHoldII) + "MODULE_CARGO_HOLD_III" -> Ok(ModuleCargoHoldIII) + "MODULE_CREW_QUARTERS_I" -> Ok(ModuleCrewQuartersI) + "MODULE_ENVOY_QUARTERS_I" -> Ok(ModuleEnvoyQuartersI) + "MODULE_PASSENGER_CABIN_I" -> Ok(ModulePassengerCabinI) + "MODULE_MICRO_REFINERY_I" -> Ok(ModuleMicroRefineryI) + "MODULE_ORE_REFINERY_I" -> Ok(ModuleOreRefineryI) + "MODULE_FUEL_REFINERY_I" -> Ok(ModuleFuelRefineryI) + "MODULE_SCIENCE_LAB_I" -> Ok(ModuleScienceLabI) + "MODULE_JUMP_DRIVE_I" -> Ok(ModuleJumpDriveI) + "MODULE_JUMP_DRIVE_II" -> Ok(ModuleJumpDriveII) + "MODULE_JUMP_DRIVE_III" -> Ok(ModuleJumpDriveIII) + "MODULE_WARP_DRIVE_I" -> Ok(ModuleWarpDriveI) + "MODULE_WARP_DRIVE_II" -> Ok(ModuleWarpDriveII) + "MODULE_WARP_DRIVE_III" -> Ok(ModuleWarpDriveIII) + "MODULE_SHIELD_GENERATOR_I" -> Ok(ModuleShieldGeneratorI) + "MODULE_SHIELD_GENERATOR_II" -> Ok(ModuleShieldGeneratorII) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ModuleSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(module_symbol) -> decode.success(module_symbol) + Error(Nil) -> decode.failure(ModuleMineralProcessorI, "ModuleSymbol") + } +} + +pub fn to_string(module_symbol: ModuleSymbol) -> String { + case module_symbol { + ModuleMineralProcessorI -> "MODULE_MINERAL_PROCESSOR_I" + ModuleGasProcessorI -> "MODULE_GAS_PROCESSOR_I" + ModuleCargoHoldI -> "MODULE_CARGO_HOLD_I" + ModuleCargoHoldII -> "MODULE_CARGO_HOLD_II" + ModuleCargoHoldIII -> "MODULE_CARGO_HOLD_III" + ModuleCrewQuartersI -> "MODULE_CREW_QUARTERS_I" + ModuleEnvoyQuartersI -> "MODULE_ENVOY_QUARTERS_I" + ModulePassengerCabinI -> "MODULE_PASSENGER_CABIN_I" + ModuleMicroRefineryI -> "MODULE_MICRO_REFINERY_I" + ModuleOreRefineryI -> "MODULE_ORE_REFINERY_I" + ModuleFuelRefineryI -> "MODULE_FUEL_REFINERY_I" + ModuleScienceLabI -> "MODULE_SCIENCE_LAB_I" + ModuleJumpDriveI -> "MODULE_JUMP_DRIVE_I" + ModuleJumpDriveII -> "MODULE_JUMP_DRIVE_II" + ModuleJumpDriveIII -> "MODULE_JUMP_DRIVE_III" + ModuleWarpDriveI -> "MODULE_WARP_DRIVE_I" + ModuleWarpDriveII -> "MODULE_WARP_DRIVE_II" + ModuleWarpDriveIII -> "MODULE_WARP_DRIVE_III" + ModuleShieldGeneratorI -> "MODULE_SHIELD_GENERATOR_I" + ModuleShieldGeneratorII -> "MODULE_SHIELD_GENERATOR_II" + } +} + +pub fn encode(module_symbol: ModuleSymbol) -> Json { + json.string(to_string(module_symbol)) +} diff --git a/src/spacetraders_models/mount_deposit.gleam b/src/spacetraders_models/mount_deposit.gleam new file mode 100644 index 0000000..8afc095 --- /dev/null +++ b/src/spacetraders_models/mount_deposit.gleam @@ -0,0 +1,70 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type MountDeposit { + QuartzSand + SiliconCrystals + PreciousStones + IceWater + AmmoniaIce + IronOre + CopperOre + SilverOre + AluminumOre + GoldOre + PlatinumOre + Diamonds + UraniteOre + MeritiumOre +} + +pub fn parse(value: String) -> Result(MountDeposit, Nil) { + case value { + "QUARTZ_SAND" -> Ok(QuartzSand) + "SILICON_CRYSTALS" -> Ok(SiliconCrystals) + "PRECIOUS_STONES" -> Ok(PreciousStones) + "ICE_WATER" -> Ok(IceWater) + "AMMONIA_ICE" -> Ok(AmmoniaIce) + "IRON_ORE" -> Ok(IronOre) + "COPPER_ORE" -> Ok(CopperOre) + "SILVER_ORE" -> Ok(SilverOre) + "ALUMINUM_ORE" -> Ok(AluminumOre) + "GOLD_ORE" -> Ok(GoldOre) + "PLATINUM_ORE" -> Ok(PlatinumOre) + "DIAMONDS" -> Ok(Diamonds) + "URANITE_ORE" -> Ok(UraniteOre) + "MERITIUM_ORE" -> Ok(MeritiumOre) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(MountDeposit) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(mount_deposit) -> decode.success(mount_deposit) + Error(Nil) -> decode.failure(QuartzSand, "MountDeposit") + } +} + +pub fn to_string(mount_deposit: MountDeposit) -> String { + case mount_deposit { + QuartzSand -> "QUARTZ_SAND" + SiliconCrystals -> "SILICON_CRYSTALS" + PreciousStones -> "PRECIOUS_STONES" + IceWater -> "ICE_WATER" + AmmoniaIce -> "AMMONIA_ICE" + IronOre -> "IRON_ORE" + CopperOre -> "COPPER_ORE" + SilverOre -> "SILVER_ORE" + AluminumOre -> "ALUMINUM_ORE" + GoldOre -> "GOLD_ORE" + PlatinumOre -> "PLATINUM_ORE" + Diamonds -> "DIAMONDS" + UraniteOre -> "URANITE_ORE" + MeritiumOre -> "MERITIUM_ORE" + } +} + +pub fn encode(mount_deposit: MountDeposit) -> Json { + json.string(to_string(mount_deposit)) +} diff --git a/src/spacetraders_models/mount_symbol.gleam b/src/spacetraders_models/mount_symbol.gleam new file mode 100644 index 0000000..29278e8 --- /dev/null +++ b/src/spacetraders_models/mount_symbol.gleam @@ -0,0 +1,73 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type MountSymbol { + MountGasSiphonI + MountGasSiphonII + MountGasSiphonIII + MountSurveyorI + MountSurveyorII + MountSurveyorIII + MountSensorArrayI + MountSensorArrayII + MountSensorArrayIII + MountMiningLaserI + MountMiningLaserII + MountMiningLaserIII + MountLaserCannonI + MountMissileLauncherI + MountTurretI +} + +pub fn parse(value: String) -> Result(MountSymbol, Nil) { + case value { + "MOUNT_GAS_SIPHON_I" -> Ok(MountGasSiphonI) + "MOUNT_GAS_SIPHON_II" -> Ok(MountGasSiphonII) + "MOUNT_GAS_SIPHON_III" -> Ok(MountGasSiphonIII) + "MOUNT_SURVEYOR_I" -> Ok(MountSurveyorI) + "MOUNT_SURVEYOR_II" -> Ok(MountSurveyorII) + "MOUNT_SURVEYOR_III" -> Ok(MountSurveyorIII) + "MOUNT_SENSOR_ARRAY_I" -> Ok(MountSensorArrayI) + "MOUNT_SENSOR_ARRAY_II" -> Ok(MountSensorArrayII) + "MOUNT_SENSOR_ARRAY_III" -> Ok(MountSensorArrayIII) + "MOUNT_MINING_LASER_I" -> Ok(MountMiningLaserI) + "MOUNT_MINING_LASER_II" -> Ok(MountMiningLaserII) + "MOUNT_MINING_LASER_III" -> Ok(MountMiningLaserIII) + "MOUNT_LASER_CANNON_I" -> Ok(MountLaserCannonI) + "MOUNT_MISSILE_LAUNCHER_I" -> Ok(MountMissileLauncherI) + "MOUNT_TURRET_I" -> Ok(MountTurretI) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(MountSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(mount_symbol) -> decode.success(mount_symbol) + Error(Nil) -> decode.failure(MountGasSiphonI, "MountSymbol") + } +} + +pub fn to_string(mount_symbol: MountSymbol) -> String { + case mount_symbol { + MountGasSiphonI -> "MOUNT_GAS_SIPHON_I" + MountGasSiphonII -> "MOUNT_GAS_SIPHON_II" + MountGasSiphonIII -> "MOUNT_GAS_SIPHON_III" + MountSurveyorI -> "MOUNT_SURVEYOR_I" + MountSurveyorII -> "MOUNT_SURVEYOR_II" + MountSurveyorIII -> "MOUNT_SURVEYOR_III" + MountSensorArrayI -> "MOUNT_SENSOR_ARRAY_I" + MountSensorArrayII -> "MOUNT_SENSOR_ARRAY_II" + MountSensorArrayIII -> "MOUNT_SENSOR_ARRAY_III" + MountMiningLaserI -> "MOUNT_MINING_LASER_I" + MountMiningLaserII -> "MOUNT_MINING_LASER_II" + MountMiningLaserIII -> "MOUNT_MINING_LASER_III" + MountLaserCannonI -> "MOUNT_LASER_CANNON_I" + MountMissileLauncherI -> "MOUNT_MISSILE_LAUNCHER_I" + MountTurretI -> "MOUNT_TURRET_I" + } +} + +pub fn encode(mount_symbol: MountSymbol) -> Json { + json.string(to_string(mount_symbol)) +} diff --git a/src/spacetraders_models/paged_data.gleam b/src/spacetraders_models/paged_data.gleam new file mode 100644 index 0000000..b715321 --- /dev/null +++ b/src/spacetraders_models/paged_data.gleam @@ -0,0 +1,12 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/meta.{type Meta} + +pub type PagedData(data) { + PagedData(data: data, meta: Meta) +} + +pub fn decoder(data_decoder: Decoder(data)) -> Decoder(PagedData(data)) { + use data <- decode.field("data", data_decoder) + use meta <- decode.field("meta", meta.decoder()) + decode.success(PagedData(data:, meta:)) +} diff --git a/src/spacetraders_models/public_agent.gleam b/src/spacetraders_models/public_agent.gleam new file mode 100644 index 0000000..3d94bb2 --- /dev/null +++ b/src/spacetraders_models/public_agent.gleam @@ -0,0 +1,32 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/agent_symbol.{type AgentSymbol} +import spacetraders_models/faction_symbol.{type FactionSymbol} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type PublicAgent { + PublicAgent( + symbol: AgentSymbol, + headquarters: WaypointSymbol, + credits: Int, + starting_faction: FactionSymbol, + ship_count: Int, + ) +} + +pub fn decoder() -> Decoder(PublicAgent) { + use symbol <- decode.field("symbol", agent_symbol.decoder()) + use headquarters <- decode.field("headquarters", waypoint_symbol.decoder()) + use credits <- decode.field("credits", decode.int) + use starting_faction <- decode.field( + "startingFaction", + faction_symbol.decoder(), + ) + use ship_count <- decode.field("shipCount", decode.int) + decode.success(PublicAgent( + symbol:, + headquarters:, + credits:, + starting_faction:, + ship_count:, + )) +} diff --git a/src/spacetraders_models/reactor_symbol.gleam b/src/spacetraders_models/reactor_symbol.gleam new file mode 100644 index 0000000..60c37f6 --- /dev/null +++ b/src/spacetraders_models/reactor_symbol.gleam @@ -0,0 +1,43 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type ReactorSymbol { + ReactorSolarI + ReactorFusionI + ReactorFissionI + ReactorChemicalI + ReactorAntimatterI +} + +pub fn parse(value: String) -> Result(ReactorSymbol, Nil) { + case value { + "REACTOR_SOLAR_I" -> Ok(ReactorSolarI) + "REACTOR_FUSION_I" -> Ok(ReactorFusionI) + "REACTOR_FISSION_I" -> Ok(ReactorFissionI) + "REACTOR_CHEMICAL_I" -> Ok(ReactorChemicalI) + "REACTOR_ANTIMATTER_I" -> Ok(ReactorAntimatterI) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ReactorSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(reactor_symbol) -> decode.success(reactor_symbol) + Error(Nil) -> decode.failure(ReactorSolarI, "ReactorSymbol") + } +} + +pub fn to_string(reactor_symbol: ReactorSymbol) -> String { + case reactor_symbol { + ReactorSolarI -> "REACTOR_SOLAR_I" + ReactorFusionI -> "REACTOR_FUSION_I" + ReactorFissionI -> "REACTOR_FISSION_I" + ReactorChemicalI -> "REACTOR_CHEMICAL_I" + ReactorAntimatterI -> "REACTOR_ANTIMATTER_I" + } +} + +pub fn encode(reactor_symbol: ReactorSymbol) -> Json { + json.string(to_string(reactor_symbol)) +} diff --git a/src/spacetraders_models/refinement_produce.gleam b/src/spacetraders_models/refinement_produce.gleam new file mode 100644 index 0000000..4ac3a23 --- /dev/null +++ b/src/spacetraders_models/refinement_produce.gleam @@ -0,0 +1,55 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type RefinementProduce { + Iron + Copper + Silver + Gold + Aluminum + Platinum + Uranite + Meritium + Fuel +} + +pub fn parse(value: String) -> Result(RefinementProduce, Nil) { + case value { + "IRON" -> Ok(Iron) + "COPPER" -> Ok(Copper) + "SILVER" -> Ok(Silver) + "GOLD" -> Ok(Gold) + "ALUMINUM" -> Ok(Aluminum) + "PLATINUM" -> Ok(Platinum) + "URANITE" -> Ok(Uranite) + "MERITIUM" -> Ok(Meritium) + "FUEL" -> Ok(Fuel) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(RefinementProduce) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(refinement_produce) -> decode.success(refinement_produce) + Error(Nil) -> decode.failure(Iron, "RefinementProduce") + } +} + +pub fn to_string(refinement_produce: RefinementProduce) -> String { + case refinement_produce { + Iron -> "IRON" + Copper -> "COPPER" + Silver -> "SILVER" + Gold -> "GOLD" + Aluminum -> "ALUMINUM" + Platinum -> "PLATINUM" + Uranite -> "URANITE" + Meritium -> "MERITIUM" + Fuel -> "FUEL" + } +} + +pub fn encode(refinement_produce: RefinementProduce) -> Json { + json.string(to_string(refinement_produce)) +} diff --git a/src/spacetraders_models/refinement_yield.gleam b/src/spacetraders_models/refinement_yield.gleam new file mode 100644 index 0000000..fa2293a --- /dev/null +++ b/src/spacetraders_models/refinement_yield.gleam @@ -0,0 +1,12 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/trade_symbol.{type TradeSymbol} + +pub type RefinementYield { + RefinementYield(trade_symbol: TradeSymbol, units: Int) +} + +pub fn decoder() -> Decoder(RefinementYield) { + use trade_symbol <- decode.field("tradeSymbol", trade_symbol.decoder()) + use units <- decode.field("units", decode.int) + decode.success(RefinementYield(trade_symbol:, units:)) +} diff --git a/src/spacetraders_models/repair_transaction.gleam b/src/spacetraders_models/repair_transaction.gleam new file mode 100644 index 0000000..cbe7a0b --- /dev/null +++ b/src/spacetraders_models/repair_transaction.gleam @@ -0,0 +1,30 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/internal/time +import spacetraders_models/ship_symbol.{type ShipSymbol} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type RepairTransaction { + RepairTransaction( + waypoint_symbol: WaypointSymbol, + ship_symbol: ShipSymbol, + total_price: Int, + timestamp: Timestamp, + ) +} + +pub fn decoder() -> Decoder(RepairTransaction) { + use waypoint_symbol <- decode.field( + "waypointSymbol", + waypoint_symbol.decoder(), + ) + use ship_symbol <- decode.field("shipSymbol", ship_symbol.decoder()) + use total_price <- decode.field("totalPrice", decode.int) + use timestamp <- decode.field("timestamp", time.rfc3339_timestamp_decoder()) + decode.success(RepairTransaction( + waypoint_symbol:, + ship_symbol:, + total_price:, + timestamp:, + )) +} diff --git a/src/spacetraders_models/scanned_ship.gleam b/src/spacetraders_models/scanned_ship.gleam new file mode 100644 index 0000000..64d45aa --- /dev/null +++ b/src/spacetraders_models/scanned_ship.gleam @@ -0,0 +1,52 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/scanned_ship_engine.{type ScannedShipEngine} +import spacetraders_models/scanned_ship_frame.{type ScannedShipFrame} +import spacetraders_models/scanned_ship_mount.{type ScannedShipMount} +import spacetraders_models/scanned_ship_reactor.{type ScannedShipReactor} +import spacetraders_models/ship_nav.{type ShipNav} +import spacetraders_models/ship_registration.{type ShipRegistration} +import spacetraders_models/ship_symbol.{type ShipSymbol} + +pub type ScannedShip { + ScannedShip( + symbol: ShipSymbol, + registration: ShipRegistration, + nav: ShipNav, + frame: Option(ScannedShipFrame), + reactor: Option(ScannedShipReactor), + engine: ScannedShipEngine, + mounts: Option(List(ScannedShipMount)), + ) +} + +pub fn decoder() -> Decoder(ScannedShip) { + use symbol <- decode.field("symbol", ship_symbol.decoder()) + use registration <- decode.field("registration", ship_registration.decoder()) + use nav <- decode.field("nav", ship_nav.decoder()) + use frame <- decode.optional_field( + "frame", + option.None, + decode.optional(scanned_ship_frame.decoder()), + ) + use reactor <- decode.optional_field( + "reactor", + option.None, + decode.optional(scanned_ship_reactor.decoder()), + ) + use engine <- decode.field("engine", scanned_ship_engine.decoder()) + use mounts <- decode.optional_field( + "mounts", + option.None, + decode.optional(decode.list(scanned_ship_mount.decoder())), + ) + decode.success(ScannedShip( + symbol:, + registration:, + nav:, + frame:, + reactor:, + engine:, + mounts:, + )) +} diff --git a/src/spacetraders_models/scanned_ship_engine.gleam b/src/spacetraders_models/scanned_ship_engine.gleam new file mode 100644 index 0000000..70cbeef --- /dev/null +++ b/src/spacetraders_models/scanned_ship_engine.gleam @@ -0,0 +1,11 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/engine_symbol.{type EngineSymbol} + +pub type ScannedShipEngine { + ScannedShipEngine(symbol: EngineSymbol) +} + +pub fn decoder() -> Decoder(ScannedShipEngine) { + use symbol <- decode.field("symbol", engine_symbol.decoder()) + decode.success(ScannedShipEngine(symbol:)) +} diff --git a/src/spacetraders_models/scanned_ship_frame.gleam b/src/spacetraders_models/scanned_ship_frame.gleam new file mode 100644 index 0000000..16ec4bc --- /dev/null +++ b/src/spacetraders_models/scanned_ship_frame.gleam @@ -0,0 +1,11 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/frame_symbol.{type FrameSymbol} + +pub type ScannedShipFrame { + ScannedShipFrame(symbol: FrameSymbol) +} + +pub fn decoder() -> Decoder(ScannedShipFrame) { + use symbol <- decode.field("symbol", frame_symbol.decoder()) + decode.success(ScannedShipFrame(symbol:)) +} diff --git a/src/spacetraders_models/scanned_ship_mount.gleam b/src/spacetraders_models/scanned_ship_mount.gleam new file mode 100644 index 0000000..70c0013 --- /dev/null +++ b/src/spacetraders_models/scanned_ship_mount.gleam @@ -0,0 +1,11 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/mount_symbol.{type MountSymbol} + +pub type ScannedShipMount { + ScannedShipMount(symbol: MountSymbol) +} + +pub fn decoder() -> Decoder(ScannedShipMount) { + use symbol <- decode.field("symbol", mount_symbol.decoder()) + decode.success(ScannedShipMount(symbol:)) +} diff --git a/src/spacetraders_models/scanned_ship_reactor.gleam b/src/spacetraders_models/scanned_ship_reactor.gleam new file mode 100644 index 0000000..5cfdc0c --- /dev/null +++ b/src/spacetraders_models/scanned_ship_reactor.gleam @@ -0,0 +1,11 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/reactor_symbol.{type ReactorSymbol} + +pub type ScannedShipReactor { + ScannedShipReactor(symbol: ReactorSymbol) +} + +pub fn decoder() -> Decoder(ScannedShipReactor) { + use symbol <- decode.field("symbol", reactor_symbol.decoder()) + decode.success(ScannedShipReactor(symbol:)) +} diff --git a/src/spacetraders_models/scanned_system.gleam b/src/spacetraders_models/scanned_system.gleam new file mode 100644 index 0000000..016f9d1 --- /dev/null +++ b/src/spacetraders_models/scanned_system.gleam @@ -0,0 +1,32 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/sector_symbol.{type SectorSymbol} +import spacetraders_models/system_symbol.{type SystemSymbol} +import spacetraders_models/system_type.{type SystemType} + +pub type ScannedSystem { + ScannedSystem( + symbol: SystemSymbol, + sector_symbol: SectorSymbol, + type_: SystemType, + x: Int, + y: Int, + distance: Int, + ) +} + +pub fn decoder() -> Decoder(ScannedSystem) { + use symbol <- decode.field("symbol", system_symbol.decoder()) + use sector_symbol <- decode.field("sectorSymbol", sector_symbol.decoder()) + use type_ <- decode.field("type", system_type.decoder()) + use x <- decode.field("x", decode.int) + use y <- decode.field("y", decode.int) + use distance <- decode.field("distance", decode.int) + decode.success(ScannedSystem( + symbol:, + sector_symbol:, + type_:, + x:, + y:, + distance:, + )) +} diff --git a/src/spacetraders_models/scanned_waypoint.gleam b/src/spacetraders_models/scanned_waypoint.gleam new file mode 100644 index 0000000..3a92831 --- /dev/null +++ b/src/spacetraders_models/scanned_waypoint.gleam @@ -0,0 +1,57 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/chart.{type Chart} +import spacetraders_models/system_symbol.{type SystemSymbol} +import spacetraders_models/waypoint_faction.{type WaypointFaction} +import spacetraders_models/waypoint_orbital.{type WaypointOrbital} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} +import spacetraders_models/waypoint_trait.{type WaypointTrait} +import spacetraders_models/waypoint_type.{type WaypointType} + +pub type ScannedWaypoint { + ScannedWaypoint( + symbol: WaypointSymbol, + type_: WaypointType, + system_symbol: SystemSymbol, + x: Int, + y: Int, + orbitals: List(WaypointOrbital), + faction: Option(WaypointFaction), + traits: List(WaypointTrait), + chart: Option(Chart), + ) +} + +pub fn decoder() -> Decoder(ScannedWaypoint) { + use symbol <- decode.field("symbol", waypoint_symbol.decoder()) + use type_ <- decode.field("type", waypoint_type.decoder()) + use system_symbol <- decode.field("systemSymbol", system_symbol.decoder()) + use x <- decode.field("x", decode.int) + use y <- decode.field("y", decode.int) + use orbitals <- decode.field( + "orbitals", + decode.list(waypoint_orbital.decoder()), + ) + use faction <- decode.optional_field( + "faction", + option.None, + decode.optional(waypoint_faction.decoder()), + ) + use traits <- decode.field("traits", decode.list(waypoint_trait.decoder())) + use chart <- decode.optional_field( + "chart", + option.None, + decode.optional(chart.decoder()), + ) + decode.success(ScannedWaypoint( + symbol:, + type_:, + system_symbol:, + x:, + y:, + orbitals:, + faction:, + traits:, + chart:, + )) +} diff --git a/src/spacetraders_models/scrap_transaction.gleam b/src/spacetraders_models/scrap_transaction.gleam new file mode 100644 index 0000000..003a6f6 --- /dev/null +++ b/src/spacetraders_models/scrap_transaction.gleam @@ -0,0 +1,30 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/internal/time +import spacetraders_models/ship_symbol.{type ShipSymbol} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type ScrapTransaction { + ScrapTransaction( + waypoint_symbol: WaypointSymbol, + ship_symbol: ShipSymbol, + total_price: Int, + timestamp: Timestamp, + ) +} + +pub fn decoder() -> Decoder(ScrapTransaction) { + use waypoint_symbol <- decode.field( + "waypointSymbol", + waypoint_symbol.decoder(), + ) + use ship_symbol <- decode.field("shipSymbol", ship_symbol.decoder()) + use total_price <- decode.field("totalPrice", decode.int) + use timestamp <- decode.field("timestamp", time.rfc3339_timestamp_decoder()) + decode.success(ScrapTransaction( + waypoint_symbol:, + ship_symbol:, + total_price:, + timestamp:, + )) +} diff --git a/src/spacetraders_models/sector_symbol.gleam b/src/spacetraders_models/sector_symbol.gleam new file mode 100644 index 0000000..1b7ec0c --- /dev/null +++ b/src/spacetraders_models/sector_symbol.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type SectorSymbol { + SectorSymbol(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(SectorSymbol, Nil) { + case string.length(value) >= min_length { + True -> Ok(SectorSymbol(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(SectorSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(sector_symbol) -> decode.success(sector_symbol) + Error(Nil) -> decode.failure(SectorSymbol("invalid"), "SectorSymbol") + } +} + +pub fn to_string(sector_symbol: SectorSymbol) -> String { + let SectorSymbol(value) = sector_symbol + value +} + +pub fn encode(sector_symbol: SectorSymbol) -> Json { + json.string(to_string(sector_symbol)) +} diff --git a/src/spacetraders_models/ship.gleam b/src/spacetraders_models/ship.gleam new file mode 100644 index 0000000..0a2c029 --- /dev/null +++ b/src/spacetraders_models/ship.gleam @@ -0,0 +1,59 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/cooldown.{type Cooldown} +import spacetraders_models/ship_cargo.{type ShipCargo} +import spacetraders_models/ship_crew.{type ShipCrew} +import spacetraders_models/ship_engine.{type ShipEngine} +import spacetraders_models/ship_frame.{type ShipFrame} +import spacetraders_models/ship_fuel.{type ShipFuel} +import spacetraders_models/ship_module.{type ShipModule} +import spacetraders_models/ship_mount.{type ShipMount} +import spacetraders_models/ship_nav.{type ShipNav} +import spacetraders_models/ship_reactor.{type ShipReactor} +import spacetraders_models/ship_registration.{type ShipRegistration} +import spacetraders_models/ship_symbol.{type ShipSymbol} + +pub type Ship { + Ship( + symbol: ShipSymbol, + registration: ShipRegistration, + nav: ShipNav, + crew: ShipCrew, + frame: ShipFrame, + reactor: ShipReactor, + engine: ShipEngine, + modules: List(ShipModule), + mounts: List(ShipMount), + cargo: ShipCargo, + cooldown: Cooldown, + fuel: ShipFuel, + ) +} + +pub fn decoder() -> Decoder(Ship) { + use symbol <- decode.field("symbol", ship_symbol.decoder()) + use registration <- decode.field("registration", ship_registration.decoder()) + use nav <- decode.field("nav", ship_nav.decoder()) + use crew <- decode.field("crew", ship_crew.decoder()) + use frame <- decode.field("frame", ship_frame.decoder()) + use reactor <- decode.field("reactor", ship_reactor.decoder()) + use engine <- decode.field("engine", ship_engine.decoder()) + use modules <- decode.field("modules", decode.list(ship_module.decoder())) + use mounts <- decode.field("mounts", decode.list(ship_mount.decoder())) + use cargo <- decode.field("cargo", ship_cargo.decoder()) + use cooldown <- decode.field("cooldown", cooldown.decoder()) + use fuel <- decode.field("fuel", ship_fuel.decoder()) + decode.success(Ship( + symbol:, + registration:, + nav:, + crew:, + frame:, + reactor:, + engine:, + modules:, + mounts:, + cargo:, + cooldown:, + fuel:, + )) +} diff --git a/src/spacetraders_models/ship_cargo.gleam b/src/spacetraders_models/ship_cargo.gleam new file mode 100644 index 0000000..fc85c6a --- /dev/null +++ b/src/spacetraders_models/ship_cargo.gleam @@ -0,0 +1,16 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/ship_cargo_item.{type ShipCargoItem} + +pub type ShipCargo { + ShipCargo(capacity: Int, units: Int, inventory: List(ShipCargoItem)) +} + +pub fn decoder() -> Decoder(ShipCargo) { + use capacity <- decode.field("capacity", decode.int) + use units <- decode.field("units", decode.int) + use inventory <- decode.field( + "inventory", + decode.list(ship_cargo_item.decoder()), + ) + decode.success(ShipCargo(capacity:, units:, inventory:)) +} diff --git a/src/spacetraders_models/ship_cargo_item.gleam b/src/spacetraders_models/ship_cargo_item.gleam new file mode 100644 index 0000000..224ec1d --- /dev/null +++ b/src/spacetraders_models/ship_cargo_item.gleam @@ -0,0 +1,19 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/trade_symbol.{type TradeSymbol} + +pub type ShipCargoItem { + ShipCargoItem( + symbol: TradeSymbol, + name: String, + description: String, + units: Int, + ) +} + +pub fn decoder() -> Decoder(ShipCargoItem) { + use symbol <- decode.field("symbol", trade_symbol.decoder()) + use name <- decode.field("name", decode.string) + use description <- decode.field("description", decode.string) + use units <- decode.field("units", decode.int) + decode.success(ShipCargoItem(symbol:, name:, description:, units:)) +} diff --git a/src/spacetraders_models/ship_component.gleam b/src/spacetraders_models/ship_component.gleam new file mode 100644 index 0000000..b1d534d --- /dev/null +++ b/src/spacetraders_models/ship_component.gleam @@ -0,0 +1,37 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type ShipComponent { + Frame + Reactor + Engine +} + +pub fn parse(value: String) -> Result(ShipComponent, Nil) { + case value { + "FRAME" -> Ok(Frame) + "REACTOR" -> Ok(Reactor) + "ENGINE" -> Ok(Engine) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ShipComponent) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(ship_component) -> decode.success(ship_component) + Error(Nil) -> decode.failure(Frame, "ShipComponent") + } +} + +pub fn to_string(ship_component: ShipComponent) -> String { + case ship_component { + Frame -> "FRAME" + Reactor -> "REACTOR" + Engine -> "ENGINE" + } +} + +pub fn encode(ship_component: ShipComponent) -> Json { + json.string(to_string(ship_component)) +} diff --git a/src/spacetraders_models/ship_component_condition.gleam b/src/spacetraders_models/ship_component_condition.gleam new file mode 100644 index 0000000..425f168 --- /dev/null +++ b/src/spacetraders_models/ship_component_condition.gleam @@ -0,0 +1,40 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/int +import gleam/json.{type Json} + +pub opaque type ShipComponentCondition { + ShipComponentCondition(Float) +} + +pub const min: Float = 0.0 + +pub const max: Float = 1.0 + +pub fn parse(value: Float) -> Result(ShipComponentCondition, Nil) { + case value >=. min && value <=. max { + True -> Ok(ShipComponentCondition(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ShipComponentCondition) { + use value <- decode.then( + decode.one_of(decode.float, [ + decode.then(decode.int, fn(i) { decode.success(int.to_float(i)) }), + ]), + ) + case parse(value) { + Ok(ship_component_condition) -> decode.success(ship_component_condition) + Error(Nil) -> + decode.failure(ShipComponentCondition(0.0), "ShipComponentCondition") + } +} + +pub fn to_float(ship_component_condition: ShipComponentCondition) -> Float { + let ShipComponentCondition(value) = ship_component_condition + value +} + +pub fn encode(ship_component_condition: ShipComponentCondition) -> Json { + json.float(to_float(ship_component_condition)) +} diff --git a/src/spacetraders_models/ship_component_integrity.gleam b/src/spacetraders_models/ship_component_integrity.gleam new file mode 100644 index 0000000..6a325f7 --- /dev/null +++ b/src/spacetraders_models/ship_component_integrity.gleam @@ -0,0 +1,40 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/int +import gleam/json.{type Json} + +pub opaque type ShipComponentIntegrity { + ShipComponentIntegrity(Float) +} + +pub const min: Float = 0.0 + +pub const max: Float = 1.0 + +pub fn parse(value: Float) -> Result(ShipComponentIntegrity, Nil) { + case value >=. min && value <=. max { + True -> Ok(ShipComponentIntegrity(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ShipComponentIntegrity) { + use value <- decode.then( + decode.one_of(decode.float, [ + decode.then(decode.int, fn(i) { decode.success(int.to_float(i)) }), + ]), + ) + case parse(value) { + Ok(ship_component_integrity) -> decode.success(ship_component_integrity) + Error(Nil) -> + decode.failure(ShipComponentIntegrity(0.0), "ShipComponentIntegrity") + } +} + +pub fn to_float(ship_component_integrity: ShipComponentIntegrity) -> Float { + let ShipComponentIntegrity(value) = ship_component_integrity + value +} + +pub fn encode(ship_component_integrity: ShipComponentIntegrity) -> Json { + json.float(to_float(ship_component_integrity)) +} diff --git a/src/spacetraders_models/ship_component_quality.gleam b/src/spacetraders_models/ship_component_quality.gleam new file mode 100644 index 0000000..24633af --- /dev/null +++ b/src/spacetraders_models/ship_component_quality.gleam @@ -0,0 +1,28 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub opaque type ShipComponentQuality { + ShipComponentQuality(Int) +} + +pub fn parse(value: Int) -> Result(ShipComponentQuality, Nil) { + Ok(ShipComponentQuality(value)) +} + +pub fn decoder() -> Decoder(ShipComponentQuality) { + use value <- decode.then(decode.int) + case parse(value) { + Ok(ship_component_quality) -> decode.success(ship_component_quality) + Error(Nil) -> + decode.failure(ShipComponentQuality(0), "ShipComponentQuality") + } +} + +pub fn to_int(ship_component_quality: ShipComponentQuality) -> Int { + let ShipComponentQuality(value) = ship_component_quality + value +} + +pub fn encode(ship_component_quality: ShipComponentQuality) -> Json { + json.int(to_int(ship_component_quality)) +} diff --git a/src/spacetraders_models/ship_condition_event.gleam b/src/spacetraders_models/ship_condition_event.gleam new file mode 100644 index 0000000..465157e --- /dev/null +++ b/src/spacetraders_models/ship_condition_event.gleam @@ -0,0 +1,22 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/ship_component.{type ShipComponent} +import spacetraders_models/ship_condition_event_symbol.{ + type ShipConditionEventSymbol, +} + +pub type ShipConditionEvent { + ShipConditionEvent( + symbol: ShipConditionEventSymbol, + component: ShipComponent, + name: String, + description: String, + ) +} + +pub fn decoder() -> Decoder(ShipConditionEvent) { + use symbol <- decode.field("symbol", ship_condition_event_symbol.decoder()) + use component <- decode.field("component", ship_component.decoder()) + use name <- decode.field("name", decode.string) + use description <- decode.field("description", decode.string) + decode.success(ShipConditionEvent(symbol:, component:, name:, description:)) +} diff --git a/src/spacetraders_models/ship_condition_event_symbol.gleam b/src/spacetraders_models/ship_condition_event_symbol.gleam new file mode 100644 index 0000000..7b8cf87 --- /dev/null +++ b/src/spacetraders_models/ship_condition_event_symbol.gleam @@ -0,0 +1,112 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type ShipConditionEventSymbol { + ReactorOverload + EnergySpikeFromMineral + SolarFlareInterference + CoolantLeak + PowerDistributionFluctuation + MagneticFieldDisruption + HullMicrometeoriteStrikes + StructuralStressFractures + CorrosiveMineralContamination + ThermalExpansionMismatch + VibrationDamageFromDrilling + ElectromagneticFieldInterference + ImpactWithExtractedDebris + FuelEfficiencyDegradation + CoolantSystemAgeing + DustMicroabrasions + ThrusterNozzleWear + ExhaustPortClogging + BearingLubricationFade + SensorCalibrationDrift + HullMicrometeoriteDamage + SpaceDebrisCollision + ThermalStress + VibrationOverload + PressureDifferentialStress + ElectromagneticSurgeEffects + AtmosphericEntryHeat +} + +pub fn parse(value: String) -> Result(ShipConditionEventSymbol, Nil) { + case value { + "REACTOR_OVERLOAD" -> Ok(ReactorOverload) + "ENERGY_SPIKE_FROM_MINERAL" -> Ok(EnergySpikeFromMineral) + "SOLAR_FLARE_INTERFERENCE" -> Ok(SolarFlareInterference) + "COOLANT_LEAK" -> Ok(CoolantLeak) + "POWER_DISTRIBUTION_FLUCTUATION" -> Ok(PowerDistributionFluctuation) + "MAGNETIC_FIELD_DISRUPTION" -> Ok(MagneticFieldDisruption) + "HULL_MICROMETEORITE_STRIKES" -> Ok(HullMicrometeoriteStrikes) + "STRUCTURAL_STRESS_FRACTURES" -> Ok(StructuralStressFractures) + "CORROSIVE_MINERAL_CONTAMINATION" -> Ok(CorrosiveMineralContamination) + "THERMAL_EXPANSION_MISMATCH" -> Ok(ThermalExpansionMismatch) + "VIBRATION_DAMAGE_FROM_DRILLING" -> Ok(VibrationDamageFromDrilling) + "ELECTROMAGNETIC_FIELD_INTERFERENCE" -> Ok(ElectromagneticFieldInterference) + "IMPACT_WITH_EXTRACTED_DEBRIS" -> Ok(ImpactWithExtractedDebris) + "FUEL_EFFICIENCY_DEGRADATION" -> Ok(FuelEfficiencyDegradation) + "COOLANT_SYSTEM_AGEING" -> Ok(CoolantSystemAgeing) + "DUST_MICROABRASIONS" -> Ok(DustMicroabrasions) + "THRUSTER_NOZZLE_WEAR" -> Ok(ThrusterNozzleWear) + "EXHAUST_PORT_CLOGGING" -> Ok(ExhaustPortClogging) + "BEARING_LUBRICATION_FADE" -> Ok(BearingLubricationFade) + "SENSOR_CALIBRATION_DRIFT" -> Ok(SensorCalibrationDrift) + "HULL_MICROMETEORITE_DAMAGE" -> Ok(HullMicrometeoriteDamage) + "SPACE_DEBRIS_COLLISION" -> Ok(SpaceDebrisCollision) + "THERMAL_STRESS" -> Ok(ThermalStress) + "VIBRATION_OVERLOAD" -> Ok(VibrationOverload) + "PRESSURE_DIFFERENTIAL_STRESS" -> Ok(PressureDifferentialStress) + "ELECTROMAGNETIC_SURGE_EFFECTS" -> Ok(ElectromagneticSurgeEffects) + "ATMOSPHERIC_ENTRY_HEAT" -> Ok(AtmosphericEntryHeat) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ShipConditionEventSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(ship_condition_event_symbol) -> + decode.success(ship_condition_event_symbol) + Error(Nil) -> decode.failure(ReactorOverload, "ShipConditionEventSymbol") + } +} + +pub fn to_string( + ship_condition_event_symbol: ShipConditionEventSymbol, +) -> String { + case ship_condition_event_symbol { + ReactorOverload -> "REACTOR_OVERLOAD" + EnergySpikeFromMineral -> "ENERGY_SPIKE_FROM_MINERAL" + SolarFlareInterference -> "SOLAR_FLARE_INTERFERENCE" + CoolantLeak -> "COOLANT_LEAK" + PowerDistributionFluctuation -> "POWER_DISTRIBUTION_FLUCTUATION" + MagneticFieldDisruption -> "MAGNETIC_FIELD_DISRUPTION" + HullMicrometeoriteStrikes -> "HULL_MICROMETEORITE_STRIKES" + StructuralStressFractures -> "STRUCTURAL_STRESS_FRACTURES" + CorrosiveMineralContamination -> "CORROSIVE_MINERAL_CONTAMINATION" + ThermalExpansionMismatch -> "THERMAL_EXPANSION_MISMATCH" + VibrationDamageFromDrilling -> "VIBRATION_DAMAGE_FROM_DRILLING" + ElectromagneticFieldInterference -> "ELECTROMAGNETIC_FIELD_INTERFERENCE" + ImpactWithExtractedDebris -> "IMPACT_WITH_EXTRACTED_DEBRIS" + FuelEfficiencyDegradation -> "FUEL_EFFICIENCY_DEGRADATION" + CoolantSystemAgeing -> "COOLANT_SYSTEM_AGEING" + DustMicroabrasions -> "DUST_MICROABRASIONS" + ThrusterNozzleWear -> "THRUSTER_NOZZLE_WEAR" + ExhaustPortClogging -> "EXHAUST_PORT_CLOGGING" + BearingLubricationFade -> "BEARING_LUBRICATION_FADE" + SensorCalibrationDrift -> "SENSOR_CALIBRATION_DRIFT" + HullMicrometeoriteDamage -> "HULL_MICROMETEORITE_DAMAGE" + SpaceDebrisCollision -> "SPACE_DEBRIS_COLLISION" + ThermalStress -> "THERMAL_STRESS" + VibrationOverload -> "VIBRATION_OVERLOAD" + PressureDifferentialStress -> "PRESSURE_DIFFERENTIAL_STRESS" + ElectromagneticSurgeEffects -> "ELECTROMAGNETIC_SURGE_EFFECTS" + AtmosphericEntryHeat -> "ATMOSPHERIC_ENTRY_HEAT" + } +} + +pub fn encode(ship_condition_event_symbol: ShipConditionEventSymbol) -> Json { + json.string(to_string(ship_condition_event_symbol)) +} diff --git a/src/spacetraders_models/ship_crew.gleam b/src/spacetraders_models/ship_crew.gleam new file mode 100644 index 0000000..bc44b04 --- /dev/null +++ b/src/spacetraders_models/ship_crew.gleam @@ -0,0 +1,30 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/crew_rotation.{type CrewRotation} + +pub type ShipCrew { + ShipCrew( + current: Int, + required: Int, + capacity: Int, + rotation: CrewRotation, + morale: Int, + wages: Int, + ) +} + +pub fn decoder() -> Decoder(ShipCrew) { + use current <- decode.field("current", decode.int) + use required <- decode.field("required", decode.int) + use capacity <- decode.field("capacity", decode.int) + use rotation <- decode.field("rotation", crew_rotation.decoder()) + use morale <- decode.field("morale", decode.int) + use wages <- decode.field("wages", decode.int) + decode.success(ShipCrew( + current:, + required:, + capacity:, + rotation:, + morale:, + wages:, + )) +} diff --git a/src/spacetraders_models/ship_engine.gleam b/src/spacetraders_models/ship_engine.gleam new file mode 100644 index 0000000..4a9a4be --- /dev/null +++ b/src/spacetraders_models/ship_engine.gleam @@ -0,0 +1,40 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/engine_symbol.{type EngineSymbol} +import spacetraders_models/ship_component_condition.{type ShipComponentCondition} +import spacetraders_models/ship_component_integrity.{type ShipComponentIntegrity} +import spacetraders_models/ship_component_quality.{type ShipComponentQuality} +import spacetraders_models/ship_requirements.{type ShipRequirements} + +pub type ShipEngine { + ShipEngine( + symbol: EngineSymbol, + name: String, + condition: ShipComponentCondition, + integrity: ShipComponentIntegrity, + description: String, + speed: Int, + requirements: ShipRequirements, + quality: ShipComponentQuality, + ) +} + +pub fn decoder() -> Decoder(ShipEngine) { + use symbol <- decode.field("symbol", engine_symbol.decoder()) + use name <- decode.field("name", decode.string) + use condition <- decode.field("condition", ship_component_condition.decoder()) + use integrity <- decode.field("integrity", ship_component_integrity.decoder()) + use description <- decode.field("description", decode.string) + use speed <- decode.field("speed", decode.int) + use requirements <- decode.field("requirements", ship_requirements.decoder()) + use quality <- decode.field("quality", ship_component_quality.decoder()) + decode.success(ShipEngine( + symbol:, + name:, + condition:, + integrity:, + description:, + speed:, + requirements:, + quality:, + )) +} diff --git a/src/spacetraders_models/ship_frame.gleam b/src/spacetraders_models/ship_frame.gleam new file mode 100644 index 0000000..633e2f0 --- /dev/null +++ b/src/spacetraders_models/ship_frame.gleam @@ -0,0 +1,46 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/frame_symbol.{type FrameSymbol} +import spacetraders_models/ship_component_condition.{type ShipComponentCondition} +import spacetraders_models/ship_component_integrity.{type ShipComponentIntegrity} +import spacetraders_models/ship_component_quality.{type ShipComponentQuality} +import spacetraders_models/ship_requirements.{type ShipRequirements} + +pub type ShipFrame { + ShipFrame( + symbol: FrameSymbol, + name: String, + condition: ShipComponentCondition, + integrity: ShipComponentIntegrity, + description: String, + module_slots: Int, + mounting_points: Int, + fuel_capacity: Int, + requirements: ShipRequirements, + quality: ShipComponentQuality, + ) +} + +pub fn decoder() -> Decoder(ShipFrame) { + use symbol <- decode.field("symbol", frame_symbol.decoder()) + use name <- decode.field("name", decode.string) + use condition <- decode.field("condition", ship_component_condition.decoder()) + use integrity <- decode.field("integrity", ship_component_integrity.decoder()) + use description <- decode.field("description", decode.string) + use module_slots <- decode.field("moduleSlots", decode.int) + use mounting_points <- decode.field("mountingPoints", decode.int) + use fuel_capacity <- decode.field("fuelCapacity", decode.int) + use requirements <- decode.field("requirements", ship_requirements.decoder()) + use quality <- decode.field("quality", ship_component_quality.decoder()) + decode.success(ShipFrame( + symbol:, + name:, + condition:, + integrity:, + description:, + module_slots:, + mounting_points:, + fuel_capacity:, + requirements:, + quality:, + )) +} diff --git a/src/spacetraders_models/ship_fuel.gleam b/src/spacetraders_models/ship_fuel.gleam new file mode 100644 index 0000000..7ece5f4 --- /dev/null +++ b/src/spacetraders_models/ship_fuel.gleam @@ -0,0 +1,18 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/ship_fuel_consumed.{type ShipFuelConsumed} + +pub type ShipFuel { + ShipFuel(current: Int, capacity: Int, consumed: Option(ShipFuelConsumed)) +} + +pub fn decoder() -> Decoder(ShipFuel) { + use current <- decode.field("current", decode.int) + use capacity <- decode.field("capacity", decode.int) + use consumed <- decode.optional_field( + "consumed", + option.None, + decode.optional(ship_fuel_consumed.decoder()), + ) + decode.success(ShipFuel(current:, capacity:, consumed:)) +} diff --git a/src/spacetraders_models/ship_fuel_consumed.gleam b/src/spacetraders_models/ship_fuel_consumed.gleam new file mode 100644 index 0000000..6b34759 --- /dev/null +++ b/src/spacetraders_models/ship_fuel_consumed.gleam @@ -0,0 +1,13 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/internal/time + +pub type ShipFuelConsumed { + ShipFuelConsumed(amount: Int, timestamp: Timestamp) +} + +pub fn decoder() -> Decoder(ShipFuelConsumed) { + use amount <- decode.field("amount", decode.int) + use timestamp <- decode.field("timestamp", time.rfc3339_timestamp_decoder()) + decode.success(ShipFuelConsumed(amount:, timestamp:)) +} diff --git a/src/spacetraders_models/ship_modification_transaction.gleam b/src/spacetraders_models/ship_modification_transaction.gleam new file mode 100644 index 0000000..cc03611 --- /dev/null +++ b/src/spacetraders_models/ship_modification_transaction.gleam @@ -0,0 +1,34 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/internal/time +import spacetraders_models/ship_symbol.{type ShipSymbol} +import spacetraders_models/trade_symbol.{type TradeSymbol} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type ShipModificationTransaction { + ShipModificationTransaction( + waypoint_symbol: WaypointSymbol, + ship_symbol: ShipSymbol, + trade_symbol: TradeSymbol, + total_price: Int, + timestamp: Timestamp, + ) +} + +pub fn decoder() -> Decoder(ShipModificationTransaction) { + use waypoint_symbol <- decode.field( + "waypointSymbol", + waypoint_symbol.decoder(), + ) + use ship_symbol <- decode.field("shipSymbol", ship_symbol.decoder()) + use trade_symbol <- decode.field("tradeSymbol", trade_symbol.decoder()) + use total_price <- decode.field("totalPrice", decode.int) + use timestamp <- decode.field("timestamp", time.rfc3339_timestamp_decoder()) + decode.success(ShipModificationTransaction( + waypoint_symbol:, + ship_symbol:, + trade_symbol:, + total_price:, + timestamp:, + )) +} diff --git a/src/spacetraders_models/ship_module.gleam b/src/spacetraders_models/ship_module.gleam new file mode 100644 index 0000000..6f3d0ef --- /dev/null +++ b/src/spacetraders_models/ship_module.gleam @@ -0,0 +1,40 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/module_symbol.{type ModuleSymbol} +import spacetraders_models/ship_requirements.{type ShipRequirements} + +pub type ShipModule { + ShipModule( + symbol: ModuleSymbol, + name: String, + description: String, + capacity: Option(Int), + range: Option(Int), + requirements: ShipRequirements, + ) +} + +pub fn decoder() -> Decoder(ShipModule) { + use symbol <- decode.field("symbol", module_symbol.decoder()) + use name <- decode.field("name", decode.string) + use description <- decode.field("description", decode.string) + use capacity <- decode.optional_field( + "capacity", + option.None, + decode.optional(decode.int), + ) + use range <- decode.optional_field( + "range", + option.None, + decode.optional(decode.int), + ) + use requirements <- decode.field("requirements", ship_requirements.decoder()) + decode.success(ShipModule( + symbol:, + name:, + description:, + capacity:, + range:, + requirements:, + )) +} diff --git a/src/spacetraders_models/ship_mount.gleam b/src/spacetraders_models/ship_mount.gleam new file mode 100644 index 0000000..e4d01e6 --- /dev/null +++ b/src/spacetraders_models/ship_mount.gleam @@ -0,0 +1,41 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/mount_deposit.{type MountDeposit} +import spacetraders_models/mount_symbol.{type MountSymbol} +import spacetraders_models/ship_requirements.{type ShipRequirements} + +pub type ShipMount { + ShipMount( + symbol: MountSymbol, + name: String, + description: String, + strength: Option(Int), + deposits: Option(List(MountDeposit)), + requirements: ShipRequirements, + ) +} + +pub fn decoder() -> Decoder(ShipMount) { + use symbol <- decode.field("symbol", mount_symbol.decoder()) + use name <- decode.field("name", decode.string) + use description <- decode.field("description", decode.string) + use strength <- decode.optional_field( + "strength", + option.None, + decode.optional(decode.int), + ) + use deposits <- decode.optional_field( + "deposits", + option.None, + decode.optional(decode.list(mount_deposit.decoder())), + ) + use requirements <- decode.field("requirements", ship_requirements.decoder()) + decode.success(ShipMount( + symbol:, + name:, + description:, + strength:, + deposits:, + requirements:, + )) +} diff --git a/src/spacetraders_models/ship_nav.gleam b/src/spacetraders_models/ship_nav.gleam new file mode 100644 index 0000000..e1c0a09 --- /dev/null +++ b/src/spacetraders_models/ship_nav.gleam @@ -0,0 +1,34 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/ship_nav_flight_mode.{type ShipNavFlightMode} +import spacetraders_models/ship_nav_route.{type ShipNavRoute} +import spacetraders_models/ship_nav_status.{type ShipNavStatus} +import spacetraders_models/system_symbol.{type SystemSymbol} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type ShipNav { + ShipNav( + system_symbol: SystemSymbol, + waypoint_symbol: WaypointSymbol, + route: ShipNavRoute, + status: ShipNavStatus, + flight_mode: ShipNavFlightMode, + ) +} + +pub fn decoder() -> Decoder(ShipNav) { + use system_symbol <- decode.field("systemSymbol", system_symbol.decoder()) + use waypoint_symbol <- decode.field( + "waypointSymbol", + waypoint_symbol.decoder(), + ) + use route <- decode.field("route", ship_nav_route.decoder()) + use status <- decode.field("status", ship_nav_status.decoder()) + use flight_mode <- decode.field("flightMode", ship_nav_flight_mode.decoder()) + decode.success(ShipNav( + system_symbol:, + waypoint_symbol:, + route:, + status:, + flight_mode:, + )) +} diff --git a/src/spacetraders_models/ship_nav_flight_mode.gleam b/src/spacetraders_models/ship_nav_flight_mode.gleam new file mode 100644 index 0000000..a540507 --- /dev/null +++ b/src/spacetraders_models/ship_nav_flight_mode.gleam @@ -0,0 +1,40 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type ShipNavFlightMode { + Drift + Stealth + Cruise + Burn +} + +pub fn parse(value: String) -> Result(ShipNavFlightMode, Nil) { + case value { + "DRIFT" -> Ok(Drift) + "STEALTH" -> Ok(Stealth) + "CRUISE" -> Ok(Cruise) + "BURN" -> Ok(Burn) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ShipNavFlightMode) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(ship_nav_flight_mode) -> decode.success(ship_nav_flight_mode) + Error(Nil) -> decode.failure(Drift, "ShipNavFlightMode") + } +} + +pub fn to_string(ship_nav_flight_mode: ShipNavFlightMode) -> String { + case ship_nav_flight_mode { + Drift -> "DRIFT" + Stealth -> "STEALTH" + Cruise -> "CRUISE" + Burn -> "BURN" + } +} + +pub fn encode(ship_nav_flight_mode: ShipNavFlightMode) -> Json { + json.string(to_string(ship_nav_flight_mode)) +} diff --git a/src/spacetraders_models/ship_nav_route.gleam b/src/spacetraders_models/ship_nav_route.gleam new file mode 100644 index 0000000..0280a55 --- /dev/null +++ b/src/spacetraders_models/ship_nav_route.gleam @@ -0,0 +1,27 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/internal/time +import spacetraders_models/ship_nav_route_waypoint.{type ShipNavRouteWaypoint} + +pub type ShipNavRoute { + ShipNavRoute( + destination: ShipNavRouteWaypoint, + origin: ShipNavRouteWaypoint, + departure_time: Timestamp, + arrival: Timestamp, + ) +} + +pub fn decoder() -> Decoder(ShipNavRoute) { + use destination <- decode.field( + "destination", + ship_nav_route_waypoint.decoder(), + ) + use origin <- decode.field("origin", ship_nav_route_waypoint.decoder()) + use departure_time <- decode.field( + "departureTime", + time.rfc3339_timestamp_decoder(), + ) + use arrival <- decode.field("arrival", time.rfc3339_timestamp_decoder()) + decode.success(ShipNavRoute(destination:, origin:, departure_time:, arrival:)) +} diff --git a/src/spacetraders_models/ship_nav_route_waypoint.gleam b/src/spacetraders_models/ship_nav_route_waypoint.gleam new file mode 100644 index 0000000..12111b6 --- /dev/null +++ b/src/spacetraders_models/ship_nav_route_waypoint.gleam @@ -0,0 +1,23 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/system_symbol.{type SystemSymbol} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} +import spacetraders_models/waypoint_type.{type WaypointType} + +pub type ShipNavRouteWaypoint { + ShipNavRouteWaypoint( + symbol: WaypointSymbol, + type_: WaypointType, + system_symbol: SystemSymbol, + x: Int, + y: Int, + ) +} + +pub fn decoder() -> Decoder(ShipNavRouteWaypoint) { + use symbol <- decode.field("symbol", waypoint_symbol.decoder()) + use type_ <- decode.field("type", waypoint_type.decoder()) + use system_symbol <- decode.field("systemSymbol", system_symbol.decoder()) + use x <- decode.field("x", decode.int) + use y <- decode.field("y", decode.int) + decode.success(ShipNavRouteWaypoint(symbol:, type_:, system_symbol:, x:, y:)) +} diff --git a/src/spacetraders_models/ship_nav_status.gleam b/src/spacetraders_models/ship_nav_status.gleam new file mode 100644 index 0000000..3e7cb7a --- /dev/null +++ b/src/spacetraders_models/ship_nav_status.gleam @@ -0,0 +1,37 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type ShipNavStatus { + InTransit + InOrbit + Docked +} + +pub fn parse(value: String) -> Result(ShipNavStatus, Nil) { + case value { + "IN_TRANSIT" -> Ok(InTransit) + "IN_ORBIT" -> Ok(InOrbit) + "DOCKED" -> Ok(Docked) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ShipNavStatus) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(ship_nav_status) -> decode.success(ship_nav_status) + Error(Nil) -> decode.failure(InTransit, "ShipNavStatus") + } +} + +pub fn to_string(ship_nav_status: ShipNavStatus) -> String { + case ship_nav_status { + InTransit -> "IN_TRANSIT" + InOrbit -> "IN_ORBIT" + Docked -> "DOCKED" + } +} + +pub fn encode(ship_nav_status: ShipNavStatus) -> Json { + json.string(to_string(ship_nav_status)) +} diff --git a/src/spacetraders_models/ship_reactor.gleam b/src/spacetraders_models/ship_reactor.gleam new file mode 100644 index 0000000..e11f859 --- /dev/null +++ b/src/spacetraders_models/ship_reactor.gleam @@ -0,0 +1,40 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/reactor_symbol.{type ReactorSymbol} +import spacetraders_models/ship_component_condition.{type ShipComponentCondition} +import spacetraders_models/ship_component_integrity.{type ShipComponentIntegrity} +import spacetraders_models/ship_component_quality.{type ShipComponentQuality} +import spacetraders_models/ship_requirements.{type ShipRequirements} + +pub type ShipReactor { + ShipReactor( + symbol: ReactorSymbol, + name: String, + condition: ShipComponentCondition, + integrity: ShipComponentIntegrity, + description: String, + power_output: Int, + requirements: ShipRequirements, + quality: ShipComponentQuality, + ) +} + +pub fn decoder() -> Decoder(ShipReactor) { + use symbol <- decode.field("symbol", reactor_symbol.decoder()) + use name <- decode.field("name", decode.string) + use condition <- decode.field("condition", ship_component_condition.decoder()) + use integrity <- decode.field("integrity", ship_component_integrity.decoder()) + use description <- decode.field("description", decode.string) + use power_output <- decode.field("powerOutput", decode.int) + use requirements <- decode.field("requirements", ship_requirements.decoder()) + use quality <- decode.field("quality", ship_component_quality.decoder()) + decode.success(ShipReactor( + symbol:, + name:, + condition:, + integrity:, + description:, + power_output:, + requirements:, + quality:, + )) +} diff --git a/src/spacetraders_models/ship_registration.gleam b/src/spacetraders_models/ship_registration.gleam new file mode 100644 index 0000000..5541ea6 --- /dev/null +++ b/src/spacetraders_models/ship_registration.gleam @@ -0,0 +1,19 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/faction_symbol.{type FactionSymbol} +import spacetraders_models/ship_role.{type ShipRole} +import spacetraders_models/ship_symbol.{type ShipSymbol} + +pub type ShipRegistration { + ShipRegistration( + name: ShipSymbol, + faction_symbol: FactionSymbol, + role: ShipRole, + ) +} + +pub fn decoder() -> Decoder(ShipRegistration) { + use name <- decode.field("name", ship_symbol.decoder()) + use faction_symbol <- decode.field("factionSymbol", faction_symbol.decoder()) + use role <- decode.field("role", ship_role.decoder()) + decode.success(ShipRegistration(name:, faction_symbol:, role:)) +} diff --git a/src/spacetraders_models/ship_requirements.gleam b/src/spacetraders_models/ship_requirements.gleam new file mode 100644 index 0000000..1c10fa6 --- /dev/null +++ b/src/spacetraders_models/ship_requirements.gleam @@ -0,0 +1,25 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} + +pub type ShipRequirements { + ShipRequirements(power: Option(Int), crew: Option(Int), slots: Option(Int)) +} + +pub fn decoder() -> Decoder(ShipRequirements) { + use power <- decode.optional_field( + "power", + option.None, + decode.optional(decode.int), + ) + use crew <- decode.optional_field( + "crew", + option.None, + decode.optional(decode.int), + ) + use slots <- decode.optional_field( + "slots", + option.None, + decode.optional(decode.int), + ) + decode.success(ShipRequirements(power:, crew:, slots:)) +} diff --git a/src/spacetraders_models/ship_role.gleam b/src/spacetraders_models/ship_role.gleam new file mode 100644 index 0000000..3d287f6 --- /dev/null +++ b/src/spacetraders_models/ship_role.gleam @@ -0,0 +1,70 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type ShipRole { + Fabricator + Harvester + Hauler + Interceptor + Excavator + Transport + Repair + Surveyor + Command + Carrier + Patrol + Satellite + Explorer + Refinery +} + +pub fn parse(value: String) -> Result(ShipRole, Nil) { + case value { + "FABRICATOR" -> Ok(Fabricator) + "HARVESTER" -> Ok(Harvester) + "HAULER" -> Ok(Hauler) + "INTERCEPTOR" -> Ok(Interceptor) + "EXCAVATOR" -> Ok(Excavator) + "TRANSPORT" -> Ok(Transport) + "REPAIR" -> Ok(Repair) + "SURVEYOR" -> Ok(Surveyor) + "COMMAND" -> Ok(Command) + "CARRIER" -> Ok(Carrier) + "PATROL" -> Ok(Patrol) + "SATELLITE" -> Ok(Satellite) + "EXPLORER" -> Ok(Explorer) + "REFINERY" -> Ok(Refinery) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ShipRole) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(ship_role) -> decode.success(ship_role) + Error(Nil) -> decode.failure(Fabricator, "ShipRole") + } +} + +pub fn to_string(ship_role: ShipRole) -> String { + case ship_role { + Fabricator -> "FABRICATOR" + Harvester -> "HARVESTER" + Hauler -> "HAULER" + Interceptor -> "INTERCEPTOR" + Excavator -> "EXCAVATOR" + Transport -> "TRANSPORT" + Repair -> "REPAIR" + Surveyor -> "SURVEYOR" + Command -> "COMMAND" + Carrier -> "CARRIER" + Patrol -> "PATROL" + Satellite -> "SATELLITE" + Explorer -> "EXPLORER" + Refinery -> "REFINERY" + } +} + +pub fn encode(ship_role: ShipRole) -> Json { + json.string(to_string(ship_role)) +} diff --git a/src/spacetraders_models/ship_symbol.gleam b/src/spacetraders_models/ship_symbol.gleam new file mode 100644 index 0000000..7b03b11 --- /dev/null +++ b/src/spacetraders_models/ship_symbol.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type ShipSymbol { + ShipSymbol(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(ShipSymbol, Nil) { + case string.length(value) >= min_length { + True -> Ok(ShipSymbol(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ShipSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(ship_symbol) -> decode.success(ship_symbol) + Error(Nil) -> decode.failure(ShipSymbol("invalid"), "ShipSymbol") + } +} + +pub fn to_string(ship_symbol: ShipSymbol) -> String { + let ShipSymbol(symbol) = ship_symbol + symbol +} + +pub fn encode(ship_symbol: ShipSymbol) -> Json { + json.string(to_string(ship_symbol)) +} diff --git a/src/spacetraders_models/ship_type.gleam b/src/spacetraders_models/ship_type.gleam new file mode 100644 index 0000000..b38f5f9 --- /dev/null +++ b/src/spacetraders_models/ship_type.gleam @@ -0,0 +1,43 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type ShipType { + ShipProbe + ShipMiningDrone + ShipSiphonDrone + ShipInterceptor + ShipLightHauler +} + +pub fn parse(value: String) -> Result(ShipType, Nil) { + case value { + "SHIP_PROBE" -> Ok(ShipProbe) + "SHIP_MINING_DRONE" -> Ok(ShipMiningDrone) + "SHIP_SIPHON_DRONE" -> Ok(ShipSiphonDrone) + "SHIP_INTERCEPTOR" -> Ok(ShipInterceptor) + "SHIP_LIGHT_HAULER" -> Ok(ShipLightHauler) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ShipType) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(ship_type) -> decode.success(ship_type) + Error(Nil) -> decode.failure(ShipProbe, "ShipType") + } +} + +pub fn to_string(ship_type: ShipType) -> String { + case ship_type { + ShipProbe -> "SHIP_PROBE" + ShipMiningDrone -> "SHIP_MINING_DRONE" + ShipSiphonDrone -> "SHIP_SIPHON_DRONE" + ShipInterceptor -> "SHIP_INTERCEPTOR" + ShipLightHauler -> "SHIP_LIGHT_HAULER" + } +} + +pub fn encode(ship_type: ShipType) -> Json { + json.string(to_string(ship_type)) +} diff --git a/src/spacetraders_models/shipyard.gleam b/src/spacetraders_models/shipyard.gleam new file mode 100644 index 0000000..86c2d66 --- /dev/null +++ b/src/spacetraders_models/shipyard.gleam @@ -0,0 +1,39 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/ship_type.{type ShipType} +import spacetraders_models/shipyard_ship.{type ShipyardShip} +import spacetraders_models/shipyard_symbol.{type ShipyardSymbol} +import spacetraders_models/shipyard_transaction.{type ShipyardTransaction} + +pub type Shipyard { + Shipyard( + symbol: ShipyardSymbol, + ship_types: List(ShipType), + transactions: Option(List(ShipyardTransaction)), + ships: Option(List(ShipyardShip)), + modifications_fee: Int, + ) +} + +pub fn decoder() -> Decoder(Shipyard) { + use symbol <- decode.field("symbol", shipyard_symbol.decoder()) + use ship_types <- decode.field("shipTypes", decode.list(ship_type.decoder())) + use transactions <- decode.optional_field( + "transactions", + option.None, + decode.optional(decode.list(shipyard_transaction.decoder())), + ) + use ships <- decode.optional_field( + "ships", + option.None, + decode.optional(decode.list(shipyard_ship.decoder())), + ) + use modifications_fee <- decode.field("modificationsFee", decode.int) + decode.success(Shipyard( + symbol:, + ship_types:, + transactions:, + ships:, + modifications_fee:, + )) +} diff --git a/src/spacetraders_models/shipyard_ship.gleam b/src/spacetraders_models/shipyard_ship.gleam new file mode 100644 index 0000000..51b5613 --- /dev/null +++ b/src/spacetraders_models/shipyard_ship.gleam @@ -0,0 +1,61 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/activity_level.{type ActivityLevel} +import spacetraders_models/ship_engine.{type ShipEngine} +import spacetraders_models/ship_frame.{type ShipFrame} +import spacetraders_models/ship_module.{type ShipModule} +import spacetraders_models/ship_mount.{type ShipMount} +import spacetraders_models/ship_reactor.{type ShipReactor} +import spacetraders_models/ship_type.{type ShipType} +import spacetraders_models/shipyard_ship_crew.{type ShipyardShipCrew} +import spacetraders_models/supply_level.{type SupplyLevel} + +pub type ShipyardShip { + ShipyardShip( + type_: ShipType, + name: String, + description: String, + activity: Option(ActivityLevel), + supply: SupplyLevel, + purchase_price: Int, + frame: ShipFrame, + reactor: ShipReactor, + engine: ShipEngine, + modules: List(ShipModule), + mounts: List(ShipMount), + crew: ShipyardShipCrew, + ) +} + +pub fn decoder() -> Decoder(ShipyardShip) { + use type_ <- decode.field("type", ship_type.decoder()) + use name <- decode.field("name", decode.string) + use description <- decode.field("description", decode.string) + use activity <- decode.optional_field( + "activity", + option.None, + decode.optional(activity_level.decoder()), + ) + use supply <- decode.field("supply", supply_level.decoder()) + use purchase_price <- decode.field("purchasePrice", decode.int) + use frame <- decode.field("frame", ship_frame.decoder()) + use reactor <- decode.field("reactor", ship_reactor.decoder()) + use engine <- decode.field("engine", ship_engine.decoder()) + use modules <- decode.field("modules", decode.list(ship_module.decoder())) + use mounts <- decode.field("mounts", decode.list(ship_mount.decoder())) + use crew <- decode.field("crew", shipyard_ship_crew.decoder()) + decode.success(ShipyardShip( + type_:, + name:, + description:, + activity:, + supply:, + purchase_price:, + frame:, + reactor:, + engine:, + modules:, + mounts:, + crew:, + )) +} diff --git a/src/spacetraders_models/shipyard_ship_crew.gleam b/src/spacetraders_models/shipyard_ship_crew.gleam new file mode 100644 index 0000000..d5f257d --- /dev/null +++ b/src/spacetraders_models/shipyard_ship_crew.gleam @@ -0,0 +1,11 @@ +import gleam/dynamic/decode.{type Decoder} + +pub type ShipyardShipCrew { + ShipCrew(required: Int, capacity: Int) +} + +pub fn decoder() -> Decoder(ShipyardShipCrew) { + use required <- decode.field("required", decode.int) + use capacity <- decode.field("capacity", decode.int) + decode.success(ShipCrew(required:, capacity:)) +} diff --git a/src/spacetraders_models/shipyard_symbol.gleam b/src/spacetraders_models/shipyard_symbol.gleam new file mode 100644 index 0000000..f3d3219 --- /dev/null +++ b/src/spacetraders_models/shipyard_symbol.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type ShipyardSymbol { + ShipyardSymbol(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(ShipyardSymbol, Nil) { + case string.length(value) >= min_length { + True -> Ok(ShipyardSymbol(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(ShipyardSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(shipyard_symbol) -> decode.success(shipyard_symbol) + Error(Nil) -> decode.failure(ShipyardSymbol("invalid"), "ShipyardSymbol") + } +} + +pub fn to_string(shipyard_symbol: ShipyardSymbol) -> String { + let ShipyardSymbol(symbol) = shipyard_symbol + symbol +} + +pub fn encode(shipyard_symbol: ShipyardSymbol) -> Json { + json.string(to_string(shipyard_symbol)) +} diff --git a/src/spacetraders_models/shipyard_transaction.gleam b/src/spacetraders_models/shipyard_transaction.gleam new file mode 100644 index 0000000..ef35949 --- /dev/null +++ b/src/spacetraders_models/shipyard_transaction.gleam @@ -0,0 +1,34 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/agent_symbol.{type AgentSymbol} +import spacetraders_models/internal/time +import spacetraders_models/ship_type.{type ShipType} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type ShipyardTransaction { + ShipyardTransaction( + waypoint_symbol: WaypointSymbol, + ship_type: ShipType, + price: Int, + agent_symbol: AgentSymbol, + timestamp: Timestamp, + ) +} + +pub fn decoder() -> Decoder(ShipyardTransaction) { + use waypoint_symbol <- decode.field( + "waypointSymbol", + waypoint_symbol.decoder(), + ) + use ship_type <- decode.field("shipType", ship_type.decoder()) + use price <- decode.field("price", decode.int) + use agent_symbol <- decode.field("agentSymbol", agent_symbol.decoder()) + use timestamp <- decode.field("timestamp", time.rfc3339_timestamp_decoder()) + decode.success(ShipyardTransaction( + waypoint_symbol:, + ship_type:, + price:, + agent_symbol:, + timestamp:, + )) +} diff --git a/src/spacetraders_models/siphon.gleam b/src/spacetraders_models/siphon.gleam new file mode 100644 index 0000000..6db7b1d --- /dev/null +++ b/src/spacetraders_models/siphon.gleam @@ -0,0 +1,13 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/ship_symbol.{type ShipSymbol} +import spacetraders_models/siphon_yield.{type SiphonYield} + +pub type Siphon { + Siphon(ship_symbol: ShipSymbol, yield: SiphonYield) +} + +pub fn decoder() -> Decoder(Siphon) { + use ship_symbol <- decode.field("shipSymbol", ship_symbol.decoder()) + use yield <- decode.field("yield", siphon_yield.decoder()) + decode.success(Siphon(ship_symbol:, yield:)) +} diff --git a/src/spacetraders_models/siphon_yield.gleam b/src/spacetraders_models/siphon_yield.gleam new file mode 100644 index 0000000..1e528b8 --- /dev/null +++ b/src/spacetraders_models/siphon_yield.gleam @@ -0,0 +1,12 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/trade_symbol.{type TradeSymbol} + +pub type SiphonYield { + SiphonYield(symbol: TradeSymbol, units: Int) +} + +pub fn decoder() -> Decoder(SiphonYield) { + use symbol <- decode.field("symbol", trade_symbol.decoder()) + use units <- decode.field("units", decode.int) + decode.success(SiphonYield(symbol:, units:)) +} diff --git a/src/spacetraders_models/spacetraders_error.gleam b/src/spacetraders_models/spacetraders_error.gleam new file mode 100644 index 0000000..695216c --- /dev/null +++ b/src/spacetraders_models/spacetraders_error.gleam @@ -0,0 +1,28 @@ +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} + +pub type SpacetradersError { + SpacetradersError( + code: Int, + message: String, + data: Option(Dynamic), + request_id: Option(String), + ) +} + +pub fn decoder() -> Decoder(SpacetradersError) { + use code <- decode.field("code", decode.int) + use message <- decode.field("message", decode.string) + use data <- decode.optional_field( + "data", + option.None, + decode.optional(decode.dynamic), + ) + use request_id <- decode.optional_field( + "requestId", + option.None, + decode.optional(decode.string), + ) + decode.success(SpacetradersError(code:, message:, data:, request_id:)) +} diff --git a/src/spacetraders_models/supply_level.gleam b/src/spacetraders_models/supply_level.gleam new file mode 100644 index 0000000..b5c68e9 --- /dev/null +++ b/src/spacetraders_models/supply_level.gleam @@ -0,0 +1,43 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type SupplyLevel { + Scarce + Limited + Moderate + High + Abundant +} + +pub fn parse(value: String) -> Result(SupplyLevel, Nil) { + case value { + "SCARCE" -> Ok(Scarce) + "LIMITED" -> Ok(Limited) + "MODERATE" -> Ok(Moderate) + "HIGH" -> Ok(High) + "ABUNDANT" -> Ok(Abundant) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(SupplyLevel) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(supply_level) -> decode.success(supply_level) + Error(Nil) -> decode.failure(Scarce, "SupplyLevel") + } +} + +pub fn to_string(supply_level: SupplyLevel) -> String { + case supply_level { + Scarce -> "SCARCE" + Limited -> "LIMITED" + Moderate -> "MODERATE" + High -> "HIGH" + Abundant -> "ABUNDANT" + } +} + +pub fn encode(supply_level: SupplyLevel) -> Json { + json.string(to_string(supply_level)) +} diff --git a/src/spacetraders_models/survey.gleam b/src/spacetraders_models/survey.gleam new file mode 100644 index 0000000..2a11d5d --- /dev/null +++ b/src/spacetraders_models/survey.gleam @@ -0,0 +1,44 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/time/calendar +import gleam/time/timestamp.{type Timestamp} +import spacetraders_models/internal/time +import spacetraders_models/survey_deposit.{type SurveyDeposit} +import spacetraders_models/survey_signature.{type SurveySignature} +import spacetraders_models/survey_size.{type SurveySize} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type Survey { + Survey( + signature: SurveySignature, + symbol: WaypointSymbol, + deposits: List(SurveyDeposit), + expiration: Timestamp, + size: SurveySize, + ) +} + +pub fn decoder() -> Decoder(Survey) { + use signature <- decode.field("signature", survey_signature.decoder()) + use symbol <- decode.field("symbol", waypoint_symbol.decoder()) + use deposits <- decode.field( + "deposits", + decode.list(survey_deposit.decoder()), + ) + use expiration <- decode.field("expiration", time.rfc3339_timestamp_decoder()) + use size <- decode.field("size", survey_size.decoder()) + decode.success(Survey(signature:, symbol:, deposits:, expiration:, size:)) +} + +pub fn encode(survey: Survey) -> Json { + json.object([ + #("signature", survey_signature.encode(survey.signature)), + #("symbol", waypoint_symbol.encode(survey.symbol)), + #("deposits", json.array(survey.deposits, survey_deposit.encode)), + #( + "expiration", + json.string(timestamp.to_rfc3339(survey.expiration, calendar.utc_offset)), + ), + #("size", survey_size.encode(survey.size)), + ]) +} diff --git a/src/spacetraders_models/survey_deposit.gleam b/src/spacetraders_models/survey_deposit.gleam new file mode 100644 index 0000000..c4eec19 --- /dev/null +++ b/src/spacetraders_models/survey_deposit.gleam @@ -0,0 +1,16 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import spacetraders_models/trade_symbol.{type TradeSymbol} + +pub type SurveyDeposit { + SurveyDeposit(symbol: TradeSymbol) +} + +pub fn decoder() -> Decoder(SurveyDeposit) { + use symbol <- decode.field("symbol", trade_symbol.decoder()) + decode.success(SurveyDeposit(symbol:)) +} + +pub fn encode(survey_deposit: SurveyDeposit) -> Json { + json.object([#("symbol", trade_symbol.encode(survey_deposit.symbol))]) +} diff --git a/src/spacetraders_models/survey_signature.gleam b/src/spacetraders_models/survey_signature.gleam new file mode 100644 index 0000000..41a329e --- /dev/null +++ b/src/spacetraders_models/survey_signature.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type SurveySignature { + SurveySignature(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(SurveySignature, Nil) { + case string.length(value) >= min_length { + True -> Ok(SurveySignature(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(SurveySignature) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(survey_signature) -> decode.success(survey_signature) + Error(Nil) -> decode.failure(SurveySignature("invalid"), "SurveySignature") + } +} + +pub fn to_string(survey_signature: SurveySignature) -> String { + let SurveySignature(value) = survey_signature + value +} + +pub fn encode(survey_signature: SurveySignature) -> Json { + json.string(to_string(survey_signature)) +} diff --git a/src/spacetraders_models/survey_size.gleam b/src/spacetraders_models/survey_size.gleam new file mode 100644 index 0000000..097f2a2 --- /dev/null +++ b/src/spacetraders_models/survey_size.gleam @@ -0,0 +1,37 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type SurveySize { + Small + Moderate + Large +} + +pub fn parse(value: String) -> Result(SurveySize, Nil) { + case value { + "SMALL" -> Ok(Small) + "MODERATE" -> Ok(Moderate) + "LARGE" -> Ok(Large) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(SurveySize) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(survey_size) -> decode.success(survey_size) + Error(Nil) -> decode.failure(Small, "SurveySize") + } +} + +pub fn to_string(survey_size: SurveySize) -> String { + case survey_size { + Small -> "SMALL" + Moderate -> "MODERATE" + Large -> "LARGE" + } +} + +pub fn encode(survey_size: SurveySize) -> Json { + json.string(to_string(survey_size)) +} diff --git a/src/spacetraders_models/system.gleam b/src/spacetraders_models/system.gleam new file mode 100644 index 0000000..6e8f8da --- /dev/null +++ b/src/spacetraders_models/system.gleam @@ -0,0 +1,59 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/constellation.{type Constellation} +import spacetraders_models/sector_symbol.{type SectorSymbol} +import spacetraders_models/system_faction.{type SystemFaction} +import spacetraders_models/system_symbol.{type SystemSymbol} +import spacetraders_models/system_type.{type SystemType} +import spacetraders_models/system_waypoint.{type SystemWaypoint} + +pub type System { + System( + constellation: Option(Constellation), + symbol: SystemSymbol, + sector_symbol: SectorSymbol, + type_: SystemType, + x: Int, + y: Int, + waypoints: List(SystemWaypoint), + factions: List(SystemFaction), + name: Option(String), + ) +} + +pub fn decoder() -> Decoder(System) { + use constellation <- decode.optional_field( + "constellation", + option.None, + decode.optional(constellation.decoder()), + ) + use symbol <- decode.field("symbol", system_symbol.decoder()) + use sector_symbol <- decode.field("sectorSymbol", sector_symbol.decoder()) + use type_ <- decode.field("type", system_type.decoder()) + use x <- decode.field("x", decode.int) + use y <- decode.field("y", decode.int) + use waypoints <- decode.field( + "waypoints", + decode.list(system_waypoint.decoder()), + ) + use factions <- decode.field( + "factions", + decode.list(system_faction.decoder()), + ) + use name <- decode.optional_field( + "name", + option.None, + decode.optional(decode.string), + ) + decode.success(System( + constellation:, + symbol:, + sector_symbol:, + type_:, + x:, + y:, + waypoints:, + factions:, + name:, + )) +} diff --git a/src/spacetraders_models/system_faction.gleam b/src/spacetraders_models/system_faction.gleam new file mode 100644 index 0000000..4a7b37e --- /dev/null +++ b/src/spacetraders_models/system_faction.gleam @@ -0,0 +1,11 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/faction_symbol.{type FactionSymbol} + +pub type SystemFaction { + SystemFaction(symbol: FactionSymbol) +} + +pub fn decoder() -> Decoder(SystemFaction) { + use symbol <- decode.field("symbol", faction_symbol.decoder()) + decode.success(SystemFaction(symbol:)) +} diff --git a/src/spacetraders_models/system_symbol.gleam b/src/spacetraders_models/system_symbol.gleam new file mode 100644 index 0000000..79a0a6a --- /dev/null +++ b/src/spacetraders_models/system_symbol.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type SystemSymbol { + SystemSymbol(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(SystemSymbol, Nil) { + case string.length(value) >= min_length { + True -> Ok(SystemSymbol(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(SystemSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(system_symbol) -> decode.success(system_symbol) + Error(Nil) -> decode.failure(SystemSymbol("invalid"), "SystemSymbol") + } +} + +pub fn to_string(system_symbol: SystemSymbol) -> String { + let SystemSymbol(symbol) = system_symbol + symbol +} + +pub fn encode(system_symbol: SystemSymbol) -> Json { + json.string(to_string(system_symbol)) +} diff --git a/src/spacetraders_models/system_type.gleam b/src/spacetraders_models/system_type.gleam new file mode 100644 index 0000000..3060089 --- /dev/null +++ b/src/spacetraders_models/system_type.gleam @@ -0,0 +1,58 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type SystemType { + NeutronStar + RedStar + OrangeStar + BlueStar + YoungStar + WhiteDwarf + BlackHole + Hypergiant + Nebula + Unstable +} + +pub fn parse(value: String) -> Result(SystemType, Nil) { + case value { + "NEUTRON_STAR" -> Ok(NeutronStar) + "RED_STAR" -> Ok(RedStar) + "ORANGE_STAR" -> Ok(OrangeStar) + "BLUE_STAR" -> Ok(BlueStar) + "YOUNG_STAR" -> Ok(YoungStar) + "WHITE_DWARF" -> Ok(WhiteDwarf) + "BLACK_HOLE" -> Ok(BlackHole) + "HYPERGIANT" -> Ok(Hypergiant) + "NEBULA" -> Ok(Nebula) + "UNSTABLE" -> Ok(Unstable) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(SystemType) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(system_type) -> decode.success(system_type) + Error(Nil) -> decode.failure(NeutronStar, "SystemType") + } +} + +pub fn to_string(system_type: SystemType) -> String { + case system_type { + NeutronStar -> "NEUTRON_STAR" + RedStar -> "RED_STAR" + OrangeStar -> "ORANGE_STAR" + BlueStar -> "BLUE_STAR" + YoungStar -> "YOUNG_STAR" + WhiteDwarf -> "WHITE_DWARF" + BlackHole -> "BLACK_HOLE" + Hypergiant -> "HYPERGIANT" + Nebula -> "NEBULA" + Unstable -> "UNSTABLE" + } +} + +pub fn encode(system_type: SystemType) -> Json { + json.string(to_string(system_type)) +} diff --git a/src/spacetraders_models/system_waypoint.gleam b/src/spacetraders_models/system_waypoint.gleam new file mode 100644 index 0000000..3e90588 --- /dev/null +++ b/src/spacetraders_models/system_waypoint.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/waypoint_orbital.{type WaypointOrbital} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} +import spacetraders_models/waypoint_type.{type WaypointType} + +pub type SystemWaypoint { + SystemWaypoint( + symbol: WaypointSymbol, + type_: WaypointType, + x: Int, + y: Int, + orbitals: List(WaypointOrbital), + orbits: Option(WaypointSymbol), + ) +} + +pub fn decoder() -> Decoder(SystemWaypoint) { + use symbol <- decode.field("symbol", waypoint_symbol.decoder()) + use type_ <- decode.field("type", waypoint_type.decoder()) + use x <- decode.field("x", decode.int) + use y <- decode.field("y", decode.int) + use orbitals <- decode.field( + "orbitals", + decode.list(waypoint_orbital.decoder()), + ) + use orbits <- decode.optional_field( + "orbits", + option.None, + decode.optional(waypoint_symbol.decoder()), + ) + decode.success(SystemWaypoint(symbol:, type_:, x:, y:, orbitals:, orbits:)) +} diff --git a/src/spacetraders_models/token_parse_error.gleam b/src/spacetraders_models/token_parse_error.gleam new file mode 100644 index 0000000..84345b9 --- /dev/null +++ b/src/spacetraders_models/token_parse_error.gleam @@ -0,0 +1,4 @@ +pub type TokenParseError { + InvalidToken + IncorrectType +} diff --git a/src/spacetraders_models/trade_good.gleam b/src/spacetraders_models/trade_good.gleam new file mode 100644 index 0000000..5871518 --- /dev/null +++ b/src/spacetraders_models/trade_good.gleam @@ -0,0 +1,13 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/trade_symbol.{type TradeSymbol} + +pub type TradeGood { + TradeGood(symbol: TradeSymbol, name: String, description: String) +} + +pub fn decoder() -> Decoder(TradeGood) { + use symbol <- decode.field("symbol", trade_symbol.decoder()) + use name <- decode.field("name", decode.string) + use description <- decode.field("description", decode.string) + decode.success(TradeGood(symbol:, name:, description:)) +} diff --git a/src/spacetraders_models/trade_good_type.gleam b/src/spacetraders_models/trade_good_type.gleam new file mode 100644 index 0000000..21fcc30 --- /dev/null +++ b/src/spacetraders_models/trade_good_type.gleam @@ -0,0 +1,37 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type TradeGoodType { + Import + Export + Exchange +} + +pub fn parse(value: String) -> Result(TradeGoodType, Nil) { + case value { + "IMPORT" -> Ok(Import) + "EXPORT" -> Ok(Export) + "EXCHANGE" -> Ok(Exchange) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(TradeGoodType) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(trade_good_type) -> decode.success(trade_good_type) + Error(Nil) -> decode.failure(Import, "TradeGoodType") + } +} + +pub fn to_string(trade_good_type: TradeGoodType) -> String { + case trade_good_type { + Import -> "IMPORT" + Export -> "EXPORT" + Exchange -> "EXCHANGE" + } +} + +pub fn encode(trade_good_type: TradeGoodType) -> Json { + json.string(to_string(trade_good_type)) +} diff --git a/src/spacetraders_models/trade_symbol.gleam b/src/spacetraders_models/trade_symbol.gleam new file mode 100644 index 0000000..05719a1 --- /dev/null +++ b/src/spacetraders_models/trade_symbol.gleam @@ -0,0 +1,463 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type TradeSymbol { + PreciousStones + QuartzSand + SiliconCrystals + AmmoniaIce + LiquidHydrogen + LiquidNitrogen + IceWater + ExoticMatter + AdvancedCircuitry + GravitonEmitters + Iron + IronOre + Copper + CopperOre + Aluminum + AluminumOre + Silver + SilverOre + Gold + GoldOre + Platinum + PlatinumOre + Diamonds + Uranite + UraniteOre + Meritium + MeritiumOre + Hydrocarbon + Antimatter + FabMats + Fertilizers + Fabrics + Food + Jewelry + Machinery + Firearms + AssaultRifles + MilitaryEquipment + Explosives + LabInstruments + Ammunition + Electronics + ShipPlating + ShipParts + Equipment + Fuel + Medicine + Drugs + Clothing + Microprocessors + Plastics + Polynucleotides + Biocomposites + QuantumStabilizers + Nanobots + AiMainframes + QuantumDrives + RoboticDrones + CyberImplants + GeneTherapeutics + NeuralChips + MoodRegulators + ViralAgents + MicroFusionGenerators + Supergrains + LaserRifles + Holographics + ShipSalvage + RelicTech + NovelLifeforms + BotanicalSpecimens + CulturalArtifacts + FrameProbe + FrameDrone + FrameInterceptor + FrameRacer + FrameFighter + FrameFrigate + FrameShuttle + FrameExplorer + FrameMiner + FrameLightFreighter + FrameHeavyFreighter + FrameTransport + FrameDestroyer + FrameCruiser + FrameCarrier + FrameBulkFreighter + ReactorSolarI + ReactorFusionI + ReactorFissionI + ReactorChemicalI + ReactorAntimatterI + EngineImpulseDriveI + EngineIonDriveI + EngineIonDriveII + EngineHyperDriveI + ModuleMineralProcessorI + ModuleGasProcessorI + ModuleCargoHoldI + ModuleCargoHoldII + ModuleCargoHoldIII + ModuleCrewQuartersI + ModuleEnvoyQuartersI + ModulePassengerCabinI + ModuleMicroRefineryI + ModuleScienceLabI + ModuleJumpDriveI + ModuleJumpDriveII + ModuleJumpDriveIII + ModuleWarpDriveI + ModuleWarpDriveII + ModuleWarpDriveIII + ModuleShieldGeneratorI + ModuleShieldGeneratorII + ModuleOreRefineryI + ModuleFuelRefineryI + MountGasSiphonI + MountGasSiphonII + MountGasSiphonIII + MountSurveyorI + MountSurveyorII + MountSurveyorIII + MountSensorArrayI + MountSensorArrayII + MountSensorArrayIII + MountMiningLaserI + MountMiningLaserII + MountMiningLaserIII + MountLaserCannonI + MountMissileLauncherI + MountTurretI + ShipProbe + ShipMiningDrone + ShipSiphonDrone + ShipInterceptor + ShipLightHauler + ShipCommandFrigate + ShipExplorer + ShipHeavyFreighter + ShipLightShuttle + ShipOreHound + ShipRefiningFreighter + ShipSurveyor + ShipBulkFreighter +} + +pub fn parse(value: String) -> Result(TradeSymbol, Nil) { + case value { + "PRECIOUS_STONES" -> Ok(PreciousStones) + "QUARTZ_SAND" -> Ok(QuartzSand) + "SILICON_CRYSTALS" -> Ok(SiliconCrystals) + "AMMONIA_ICE" -> Ok(AmmoniaIce) + "LIQUID_HYDROGEN" -> Ok(LiquidHydrogen) + "LIQUID_NITROGEN" -> Ok(LiquidNitrogen) + "ICE_WATER" -> Ok(IceWater) + "EXOTIC_MATTER" -> Ok(ExoticMatter) + "ADVANCED_CIRCUITRY" -> Ok(AdvancedCircuitry) + "GRAVITON_EMITTERS" -> Ok(GravitonEmitters) + "IRON" -> Ok(Iron) + "IRON_ORE" -> Ok(IronOre) + "COPPER" -> Ok(Copper) + "COPPER_ORE" -> Ok(CopperOre) + "ALUMINUM" -> Ok(Aluminum) + "ALUMINUM_ORE" -> Ok(AluminumOre) + "SILVER" -> Ok(Silver) + "SILVER_ORE" -> Ok(SilverOre) + "GOLD" -> Ok(Gold) + "GOLD_ORE" -> Ok(GoldOre) + "PLATINUM" -> Ok(Platinum) + "PLATINUM_ORE" -> Ok(PlatinumOre) + "DIAMONDS" -> Ok(Diamonds) + "URANITE" -> Ok(Uranite) + "URANITE_ORE" -> Ok(UraniteOre) + "MERITIUM" -> Ok(Meritium) + "MERITIUM_ORE" -> Ok(MeritiumOre) + "HYDROCARBON" -> Ok(Hydrocarbon) + "ANTIMATTER" -> Ok(Antimatter) + "FAB_MATS" -> Ok(FabMats) + "FERTILIZERS" -> Ok(Fertilizers) + "FABRICS" -> Ok(Fabrics) + "FOOD" -> Ok(Food) + "JEWELRY" -> Ok(Jewelry) + "MACHINERY" -> Ok(Machinery) + "FIREARMS" -> Ok(Firearms) + "ASSAULT_RIFLES" -> Ok(AssaultRifles) + "MILITARY_EQUIPMENT" -> Ok(MilitaryEquipment) + "EXPLOSIVES" -> Ok(Explosives) + "LAB_INSTRUMENTS" -> Ok(LabInstruments) + "AMMUNITION" -> Ok(Ammunition) + "ELECTRONICS" -> Ok(Electronics) + "SHIP_PLATING" -> Ok(ShipPlating) + "SHIP_PARTS" -> Ok(ShipParts) + "EQUIPMENT" -> Ok(Equipment) + "FUEL" -> Ok(Fuel) + "MEDICINE" -> Ok(Medicine) + "DRUGS" -> Ok(Drugs) + "CLOTHING" -> Ok(Clothing) + "MICROPROCESSORS" -> Ok(Microprocessors) + "PLASTICS" -> Ok(Plastics) + "POLYNUCLEOTIDES" -> Ok(Polynucleotides) + "BIOCOMPOSITES" -> Ok(Biocomposites) + "QUANTUM_STABILIZERS" -> Ok(QuantumStabilizers) + "NANOBOTS" -> Ok(Nanobots) + "AI_MAINFRAMES" -> Ok(AiMainframes) + "QUANTUM_DRIVES" -> Ok(QuantumDrives) + "ROBOTIC_DRONES" -> Ok(RoboticDrones) + "CYBER_IMPLANTS" -> Ok(CyberImplants) + "GENE_THERAPEUTICS" -> Ok(GeneTherapeutics) + "NEURAL_CHIPS" -> Ok(NeuralChips) + "MOOD_REGULATORS" -> Ok(MoodRegulators) + "VIRAL_AGENTS" -> Ok(ViralAgents) + "MICRO_FUSION_GENERATORS" -> Ok(MicroFusionGenerators) + "SUPERGRAINS" -> Ok(Supergrains) + "LASER_RIFLES" -> Ok(LaserRifles) + "HOLOGRAPHICS" -> Ok(Holographics) + "SHIP_SALVAGE" -> Ok(ShipSalvage) + "RELIC_TECH" -> Ok(RelicTech) + "NOVEL_LIFEFORMS" -> Ok(NovelLifeforms) + "BOTANICAL_SPECIMENS" -> Ok(BotanicalSpecimens) + "CULTURAL_ARTIFACTS" -> Ok(CulturalArtifacts) + "FRAME_PROBE" -> Ok(FrameProbe) + "FRAME_DRONE" -> Ok(FrameDrone) + "FRAME_INTERCEPTOR" -> Ok(FrameInterceptor) + "FRAME_RACER" -> Ok(FrameRacer) + "FRAME_FIGHTER" -> Ok(FrameFighter) + "FRAME_FRIGATE" -> Ok(FrameFrigate) + "FRAME_SHUTTLE" -> Ok(FrameShuttle) + "FRAME_EXPLORER" -> Ok(FrameExplorer) + "FRAME_MINER" -> Ok(FrameMiner) + "FRAME_LIGHT_FREIGHTER" -> Ok(FrameLightFreighter) + "FRAME_HEAVY_FREIGHTER" -> Ok(FrameHeavyFreighter) + "FRAME_TRANSPORT" -> Ok(FrameTransport) + "FRAME_DESTROYER" -> Ok(FrameDestroyer) + "FRAME_CRUISER" -> Ok(FrameCruiser) + "FRAME_CARRIER" -> Ok(FrameCarrier) + "FRAME_BULK_FREIGHTER" -> Ok(FrameBulkFreighter) + "REACTOR_SOLAR_I" -> Ok(ReactorSolarI) + "REACTOR_FUSION_I" -> Ok(ReactorFusionI) + "REACTOR_FISSION_I" -> Ok(ReactorFissionI) + "REACTOR_CHEMICAL_I" -> Ok(ReactorChemicalI) + "REACTOR_ANTIMATTER_I" -> Ok(ReactorAntimatterI) + "ENGINE_IMPULSE_DRIVE_I" -> Ok(EngineImpulseDriveI) + "ENGINE_ION_DRIVE_I" -> Ok(EngineIonDriveI) + "ENGINE_ION_DRIVE_II" -> Ok(EngineIonDriveII) + "ENGINE_HYPER_DRIVE_I" -> Ok(EngineHyperDriveI) + "MODULE_MINERAL_PROCESSOR_I" -> Ok(ModuleMineralProcessorI) + "MODULE_GAS_PROCESSOR_I" -> Ok(ModuleGasProcessorI) + "MODULE_CARGO_HOLD_I" -> Ok(ModuleCargoHoldI) + "MODULE_CARGO_HOLD_II" -> Ok(ModuleCargoHoldII) + "MODULE_CARGO_HOLD_III" -> Ok(ModuleCargoHoldIII) + "MODULE_CREW_QUARTERS_I" -> Ok(ModuleCrewQuartersI) + "MODULE_ENVOY_QUARTERS_I" -> Ok(ModuleEnvoyQuartersI) + "MODULE_PASSENGER_CABIN_I" -> Ok(ModulePassengerCabinI) + "MODULE_MICRO_REFINERY_I" -> Ok(ModuleMicroRefineryI) + "MODULE_SCIENCE_LAB_I" -> Ok(ModuleScienceLabI) + "MODULE_JUMP_DRIVE_I" -> Ok(ModuleJumpDriveI) + "MODULE_JUMP_DRIVE_II" -> Ok(ModuleJumpDriveII) + "MODULE_JUMP_DRIVE_III" -> Ok(ModuleJumpDriveIII) + "MODULE_WARP_DRIVE_I" -> Ok(ModuleWarpDriveI) + "MODULE_WARP_DRIVE_II" -> Ok(ModuleWarpDriveII) + "MODULE_WARP_DRIVE_III" -> Ok(ModuleWarpDriveIII) + "MODULE_SHIELD_GENERATOR_I" -> Ok(ModuleShieldGeneratorI) + "MODULE_SHIELD_GENERATOR_II" -> Ok(ModuleShieldGeneratorII) + "MODULE_ORE_REFINERY_I" -> Ok(ModuleOreRefineryI) + "MODULE_FUEL_REFINERY_I" -> Ok(ModuleFuelRefineryI) + "MOUNT_GAS_SIPHON_I" -> Ok(MountGasSiphonI) + "MOUNT_GAS_SIPHON_II" -> Ok(MountGasSiphonII) + "MOUNT_GAS_SIPHON_III" -> Ok(MountGasSiphonIII) + "MOUNT_SURVEYOR_I" -> Ok(MountSurveyorI) + "MOUNT_SURVEYOR_II" -> Ok(MountSurveyorII) + "MOUNT_SURVEYOR_III" -> Ok(MountSurveyorIII) + "MOUNT_SENSOR_ARRAY_I" -> Ok(MountSensorArrayI) + "MOUNT_SENSOR_ARRAY_II" -> Ok(MountSensorArrayII) + "MOUNT_SENSOR_ARRAY_III" -> Ok(MountSensorArrayIII) + "MOUNT_MINING_LASER_I" -> Ok(MountMiningLaserI) + "MOUNT_MINING_LASER_II" -> Ok(MountMiningLaserII) + "MOUNT_MINING_LASER_III" -> Ok(MountMiningLaserIII) + "MOUNT_LASER_CANNON_I" -> Ok(MountLaserCannonI) + "MOUNT_MISSILE_LAUNCHER_I" -> Ok(MountMissileLauncherI) + "MOUNT_TURRET_I" -> Ok(MountTurretI) + "SHIP_PROBE" -> Ok(ShipProbe) + "SHIP_MINING_DRONE" -> Ok(ShipMiningDrone) + "SHIP_SIPHON_DRONE" -> Ok(ShipSiphonDrone) + "SHIP_INTERCEPTOR" -> Ok(ShipInterceptor) + "SHIP_LIGHT_HAULER" -> Ok(ShipLightHauler) + "SHIP_COMMAND_FRIGATE" -> Ok(ShipCommandFrigate) + "SHIP_EXPLORER" -> Ok(ShipExplorer) + "SHIP_HEAVY_FREIGHTER" -> Ok(ShipHeavyFreighter) + "SHIP_LIGHT_SHUTTLE" -> Ok(ShipLightShuttle) + "SHIP_ORE_HOUND" -> Ok(ShipOreHound) + "SHIP_REFINING_FREIGHTER" -> Ok(ShipRefiningFreighter) + "SHIP_SURVEYOR" -> Ok(ShipSurveyor) + "SHIP_BULK_FREIGHTER" -> Ok(ShipBulkFreighter) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(TradeSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(trade_symbol) -> decode.success(trade_symbol) + Error(Nil) -> decode.failure(PreciousStones, "TradeSymbol") + } +} + +pub fn to_string(trade_symbol: TradeSymbol) -> String { + case trade_symbol { + PreciousStones -> "PRECIOUS_STONES" + QuartzSand -> "QUARTZ_SAND" + SiliconCrystals -> "SILICON_CRYSTALS" + AmmoniaIce -> "AMMONIA_ICE" + LiquidHydrogen -> "LIQUID_HYDROGEN" + LiquidNitrogen -> "LIQUID_NITROGEN" + IceWater -> "ICE_WATER" + ExoticMatter -> "EXOTIC_MATTER" + AdvancedCircuitry -> "ADVANCED_CIRCUITRY" + GravitonEmitters -> "GRAVITON_EMITTERS" + Iron -> "IRON" + IronOre -> "IRON_ORE" + Copper -> "COPPER" + CopperOre -> "COPPER_ORE" + Aluminum -> "ALUMINUM" + AluminumOre -> "ALUMINUM_ORE" + Silver -> "SILVER" + SilverOre -> "SILVER_ORE" + Gold -> "GOLD" + GoldOre -> "GOLD_ORE" + Platinum -> "PLATINUM" + PlatinumOre -> "PLATINUM_ORE" + Diamonds -> "DIAMONDS" + Uranite -> "URANITE" + UraniteOre -> "URANITE_ORE" + Meritium -> "MERITIUM" + MeritiumOre -> "MERITIUM_ORE" + Hydrocarbon -> "HYDROCARBON" + Antimatter -> "ANTIMATTER" + FabMats -> "FAB_MATS" + Fertilizers -> "FERTILIZERS" + Fabrics -> "FABRICS" + Food -> "FOOD" + Jewelry -> "JEWELRY" + Machinery -> "MACHINERY" + Firearms -> "FIREARMS" + AssaultRifles -> "ASSAULT_RIFLES" + MilitaryEquipment -> "MILITARY_EQUIPMENT" + Explosives -> "EXPLOSIVES" + LabInstruments -> "LAB_INSTRUMENTS" + Ammunition -> "AMMUNITION" + Electronics -> "ELECTRONICS" + ShipPlating -> "SHIP_PLATING" + ShipParts -> "SHIP_PARTS" + Equipment -> "EQUIPMENT" + Fuel -> "FUEL" + Medicine -> "MEDICINE" + Drugs -> "DRUGS" + Clothing -> "CLOTHING" + Microprocessors -> "MICROPROCESSORS" + Plastics -> "PLASTICS" + Polynucleotides -> "POLYNUCLEOTIDES" + Biocomposites -> "BIOCOMPOSITES" + QuantumStabilizers -> "QUANTUM_STABILIZERS" + Nanobots -> "NANOBOTS" + AiMainframes -> "AI_MAINFRAMES" + QuantumDrives -> "QUANTUM_DRIVES" + RoboticDrones -> "ROBOTIC_DRONES" + CyberImplants -> "CYBER_IMPLANTS" + GeneTherapeutics -> "GENE_THERAPEUTICS" + NeuralChips -> "NEURAL_CHIPS" + MoodRegulators -> "MOOD_REGULATORS" + ViralAgents -> "VIRAL_AGENTS" + MicroFusionGenerators -> "MICRO_FUSION_GENERATORS" + Supergrains -> "SUPERGRAINS" + LaserRifles -> "LASER_RIFLES" + Holographics -> "HOLOGRAPHICS" + ShipSalvage -> "SHIP_SALVAGE" + RelicTech -> "RELIC_TECH" + NovelLifeforms -> "NOVEL_LIFEFORMS" + BotanicalSpecimens -> "BOTANICAL_SPECIMENS" + CulturalArtifacts -> "CULTURAL_ARTIFACTS" + FrameProbe -> "FRAME_PROBE" + FrameDrone -> "FRAME_DRONE" + FrameInterceptor -> "FRAME_INTERCEPTOR" + FrameRacer -> "FRAME_RACER" + FrameFighter -> "FRAME_FIGHTER" + FrameFrigate -> "FRAME_FRIGATE" + FrameShuttle -> "FRAME_SHUTTLE" + FrameExplorer -> "FRAME_EXPLORER" + FrameMiner -> "FRAME_MINER" + FrameLightFreighter -> "FRAME_LIGHT_FREIGHTER" + FrameHeavyFreighter -> "FRAME_HEAVY_FREIGHTER" + FrameTransport -> "FRAME_TRANSPORT" + FrameDestroyer -> "FRAME_DESTROYER" + FrameCruiser -> "FRAME_CRUISER" + FrameCarrier -> "FRAME_CARRIER" + FrameBulkFreighter -> "FRAME_BULK_FREIGHTER" + ReactorSolarI -> "REACTOR_SOLAR_I" + ReactorFusionI -> "REACTOR_FUSION_I" + ReactorFissionI -> "REACTOR_FISSION_I" + ReactorChemicalI -> "REACTOR_CHEMICAL_I" + ReactorAntimatterI -> "REACTOR_ANTIMATTER_I" + EngineImpulseDriveI -> "ENGINE_IMPULSE_DRIVE_I" + EngineIonDriveI -> "ENGINE_ION_DRIVE_I" + EngineIonDriveII -> "ENGINE_ION_DRIVE_II" + EngineHyperDriveI -> "ENGINE_HYPER_DRIVE_I" + ModuleMineralProcessorI -> "MODULE_MINERAL_PROCESSOR_I" + ModuleGasProcessorI -> "MODULE_GAS_PROCESSOR_I" + ModuleCargoHoldI -> "MODULE_CARGO_HOLD_I" + ModuleCargoHoldII -> "MODULE_CARGO_HOLD_II" + ModuleCargoHoldIII -> "MODULE_CARGO_HOLD_III" + ModuleCrewQuartersI -> "MODULE_CREW_QUARTERS_I" + ModuleEnvoyQuartersI -> "MODULE_ENVOY_QUARTERS_I" + ModulePassengerCabinI -> "MODULE_PASSENGER_CABIN_I" + ModuleMicroRefineryI -> "MODULE_MICRO_REFINERY_I" + ModuleScienceLabI -> "MODULE_SCIENCE_LAB_I" + ModuleJumpDriveI -> "MODULE_JUMP_DRIVE_I" + ModuleJumpDriveII -> "MODULE_JUMP_DRIVE_II" + ModuleJumpDriveIII -> "MODULE_JUMP_DRIVE_III" + ModuleWarpDriveI -> "MODULE_WARP_DRIVE_I" + ModuleWarpDriveII -> "MODULE_WARP_DRIVE_II" + ModuleWarpDriveIII -> "MODULE_WARP_DRIVE_III" + ModuleShieldGeneratorI -> "MODULE_SHIELD_GENERATOR_I" + ModuleShieldGeneratorII -> "MODULE_SHIELD_GENERATOR_II" + ModuleOreRefineryI -> "MODULE_ORE_REFINERY_I" + ModuleFuelRefineryI -> "MODULE_FUEL_REFINERY_I" + MountGasSiphonI -> "MOUNT_GAS_SIPHON_I" + MountGasSiphonII -> "MOUNT_GAS_SIPHON_II" + MountGasSiphonIII -> "MOUNT_GAS_SIPHON_III" + MountSurveyorI -> "MOUNT_SURVEYOR_I" + MountSurveyorII -> "MOUNT_SURVEYOR_II" + MountSurveyorIII -> "MOUNT_SURVEYOR_III" + MountSensorArrayI -> "MOUNT_SENSOR_ARRAY_I" + MountSensorArrayII -> "MOUNT_SENSOR_ARRAY_II" + MountSensorArrayIII -> "MOUNT_SENSOR_ARRAY_III" + MountMiningLaserI -> "MOUNT_MINING_LASER_I" + MountMiningLaserII -> "MOUNT_MINING_LASER_II" + MountMiningLaserIII -> "MOUNT_MINING_LASER_III" + MountLaserCannonI -> "MOUNT_LASER_CANNON_I" + MountMissileLauncherI -> "MOUNT_MISSILE_LAUNCHER_I" + MountTurretI -> "MOUNT_TURRET_I" + ShipProbe -> "SHIP_PROBE" + ShipMiningDrone -> "SHIP_MINING_DRONE" + ShipSiphonDrone -> "SHIP_SIPHON_DRONE" + ShipInterceptor -> "SHIP_INTERCEPTOR" + ShipLightHauler -> "SHIP_LIGHT_HAULER" + ShipCommandFrigate -> "SHIP_COMMAND_FRIGATE" + ShipExplorer -> "SHIP_EXPLORER" + ShipHeavyFreighter -> "SHIP_HEAVY_FREIGHTER" + ShipLightShuttle -> "SHIP_LIGHT_SHUTTLE" + ShipOreHound -> "SHIP_ORE_HOUND" + ShipRefiningFreighter -> "SHIP_REFINING_FREIGHTER" + ShipSurveyor -> "SHIP_SURVEYOR" + ShipBulkFreighter -> "SHIP_BULK_FREIGHTER" + } +} + +pub fn encode(trade_symbol: TradeSymbol) -> Json { + json.string(to_string(trade_symbol)) +} diff --git a/src/spacetraders_models/transaction_type.gleam b/src/spacetraders_models/transaction_type.gleam new file mode 100644 index 0000000..b2d8abb --- /dev/null +++ b/src/spacetraders_models/transaction_type.gleam @@ -0,0 +1,34 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type TransactionType { + Purchase + Sell +} + +pub fn parse(value: String) -> Result(TransactionType, Nil) { + case value { + "PURCHASE" -> Ok(Purchase) + "SELL" -> Ok(Sell) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(TransactionType) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(transaction_type) -> decode.success(transaction_type) + Error(Nil) -> decode.failure(Purchase, "TransactionType") + } +} + +pub fn to_string(transaction_type: TransactionType) -> String { + case transaction_type { + Purchase -> "PURCHASE" + Sell -> "SELL" + } +} + +pub fn encode(transaction_type: TransactionType) -> Json { + json.string(to_string(transaction_type)) +} diff --git a/src/spacetraders_models/waypoint.gleam b/src/spacetraders_models/waypoint.gleam new file mode 100644 index 0000000..e56d01b --- /dev/null +++ b/src/spacetraders_models/waypoint.gleam @@ -0,0 +1,75 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/option.{type Option} +import spacetraders_models/chart.{type Chart} +import spacetraders_models/system_symbol.{type SystemSymbol} +import spacetraders_models/waypoint_faction.{type WaypointFaction} +import spacetraders_models/waypoint_modifier.{type WaypointModifier} +import spacetraders_models/waypoint_orbital.{type WaypointOrbital} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} +import spacetraders_models/waypoint_trait.{type WaypointTrait} +import spacetraders_models/waypoint_type.{type WaypointType} + +pub type Waypoint { + Waypoint( + symbol: WaypointSymbol, + type_: WaypointType, + system_symbol: SystemSymbol, + x: Int, + y: Int, + orbitals: List(WaypointOrbital), + orbits: Option(WaypointSymbol), + faction: Option(WaypointFaction), + traits: List(WaypointTrait), + modifiers: Option(List(WaypointModifier)), + chart: Option(Chart), + is_under_construction: Bool, + ) +} + +pub fn decoder() -> Decoder(Waypoint) { + use symbol <- decode.field("symbol", waypoint_symbol.decoder()) + use type_ <- decode.field("type", waypoint_type.decoder()) + use system_symbol <- decode.field("systemSymbol", system_symbol.decoder()) + use x <- decode.field("x", decode.int) + use y <- decode.field("y", decode.int) + use orbitals <- decode.field( + "orbitals", + decode.list(waypoint_orbital.decoder()), + ) + use orbits <- decode.optional_field( + "orbits", + option.None, + decode.optional(waypoint_symbol.decoder()), + ) + use chart <- decode.optional_field( + "chart", + option.None, + decode.optional(chart.decoder()), + ) + use faction <- decode.optional_field( + "faction", + option.None, + decode.optional(waypoint_faction.decoder()), + ) + use traits <- decode.field("traits", decode.list(waypoint_trait.decoder())) + use modifiers <- decode.optional_field( + "modifiers", + option.None, + decode.optional(decode.list(waypoint_modifier.decoder())), + ) + use is_under_construction <- decode.field("isUnderConstruction", decode.bool) + decode.success(Waypoint( + symbol:, + type_:, + system_symbol:, + x:, + y:, + orbitals:, + orbits:, + faction:, + traits:, + modifiers:, + chart:, + is_under_construction:, + )) +} diff --git a/src/spacetraders_models/waypoint_faction.gleam b/src/spacetraders_models/waypoint_faction.gleam new file mode 100644 index 0000000..92ae2c4 --- /dev/null +++ b/src/spacetraders_models/waypoint_faction.gleam @@ -0,0 +1,11 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/faction_symbol.{type FactionSymbol} + +pub type WaypointFaction { + WaypointFaction(symbol: FactionSymbol) +} + +pub fn decoder() -> Decoder(WaypointFaction) { + use symbol <- decode.field("symbol", faction_symbol.decoder()) + decode.success(WaypointFaction(symbol:)) +} diff --git a/src/spacetraders_models/waypoint_modifier.gleam b/src/spacetraders_models/waypoint_modifier.gleam new file mode 100644 index 0000000..891452d --- /dev/null +++ b/src/spacetraders_models/waypoint_modifier.gleam @@ -0,0 +1,17 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/waypoint_modifier_symbol.{type WaypointModifierSymbol} + +pub type WaypointModifier { + WaypointModifier( + symbol: WaypointModifierSymbol, + name: String, + description: String, + ) +} + +pub fn decoder() -> Decoder(WaypointModifier) { + use symbol <- decode.field("symbol", waypoint_modifier_symbol.decoder()) + use name <- decode.field("name", decode.string) + use description <- decode.field("description", decode.string) + decode.success(WaypointModifier(symbol:, name:, description:)) +} diff --git a/src/spacetraders_models/waypoint_modifier_symbol.gleam b/src/spacetraders_models/waypoint_modifier_symbol.gleam new file mode 100644 index 0000000..f75da52 --- /dev/null +++ b/src/spacetraders_models/waypoint_modifier_symbol.gleam @@ -0,0 +1,43 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type WaypointModifierSymbol { + Stripped + Unstable + RadiationLeak + CriticalLimit + CivilUnrest +} + +pub fn parse(value: String) -> Result(WaypointModifierSymbol, Nil) { + case value { + "STRIPPED" -> Ok(Stripped) + "UNSTABLE" -> Ok(Unstable) + "RADIATION_LEAK" -> Ok(RadiationLeak) + "CRITICAL_LIMIT" -> Ok(CriticalLimit) + "CIVIL_UNREST" -> Ok(CivilUnrest) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(WaypointModifierSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(waypoint_modifier_symbol) -> decode.success(waypoint_modifier_symbol) + Error(Nil) -> decode.failure(Stripped, "WaypointModifierSymbol") + } +} + +pub fn to_string(waypoint_modifier_symbol: WaypointModifierSymbol) -> String { + case waypoint_modifier_symbol { + Stripped -> "STRIPPED" + Unstable -> "UNSTABLE" + RadiationLeak -> "RADIATION_LEAK" + CriticalLimit -> "CRITICAL_LIMIT" + CivilUnrest -> "CIVIL_UNREST" + } +} + +pub fn encode(waypoint_modifier_symbol: WaypointModifierSymbol) -> Json { + json.string(to_string(waypoint_modifier_symbol)) +} diff --git a/src/spacetraders_models/waypoint_orbital.gleam b/src/spacetraders_models/waypoint_orbital.gleam new file mode 100644 index 0000000..668bf1f --- /dev/null +++ b/src/spacetraders_models/waypoint_orbital.gleam @@ -0,0 +1,11 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/waypoint_symbol.{type WaypointSymbol} + +pub type WaypointOrbital { + WaypointOrbital(symbol: WaypointSymbol) +} + +pub fn decoder() -> Decoder(WaypointOrbital) { + use symbol <- decode.field("symbol", waypoint_symbol.decoder()) + decode.success(WaypointOrbital(symbol:)) +} diff --git a/src/spacetraders_models/waypoint_symbol.gleam b/src/spacetraders_models/waypoint_symbol.gleam new file mode 100644 index 0000000..30279c1 --- /dev/null +++ b/src/spacetraders_models/waypoint_symbol.gleam @@ -0,0 +1,33 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} +import gleam/string + +pub opaque type WaypointSymbol { + WaypointSymbol(String) +} + +pub const min_length: Int = 1 + +pub fn parse(value: String) -> Result(WaypointSymbol, Nil) { + case string.length(value) >= min_length { + True -> Ok(WaypointSymbol(value)) + False -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(WaypointSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(waypoint_symbol) -> decode.success(waypoint_symbol) + Error(Nil) -> decode.failure(WaypointSymbol("invalid"), "WaypointSymbol") + } +} + +pub fn to_string(waypoint_symbol: WaypointSymbol) -> String { + let WaypointSymbol(symbol) = waypoint_symbol + symbol +} + +pub fn encode(waypoint_symbol: WaypointSymbol) -> Json { + json.string(to_string(waypoint_symbol)) +} diff --git a/src/spacetraders_models/waypoint_trait.gleam b/src/spacetraders_models/waypoint_trait.gleam new file mode 100644 index 0000000..375b8b1 --- /dev/null +++ b/src/spacetraders_models/waypoint_trait.gleam @@ -0,0 +1,13 @@ +import gleam/dynamic/decode.{type Decoder} +import spacetraders_models/waypoint_trait_symbol.{type WaypointTraitSymbol} + +pub type WaypointTrait { + WaypointTrait(symbol: WaypointTraitSymbol, name: String, description: String) +} + +pub fn decoder() -> Decoder(WaypointTrait) { + use symbol <- decode.field("symbol", waypoint_trait_symbol.decoder()) + use name <- decode.field("name", decode.string) + use description <- decode.field("description", decode.string) + decode.success(WaypointTrait(symbol:, name:, description:)) +} diff --git a/src/spacetraders_models/waypoint_trait_symbol.gleam b/src/spacetraders_models/waypoint_trait_symbol.gleam new file mode 100644 index 0000000..9ba5723 --- /dev/null +++ b/src/spacetraders_models/waypoint_trait_symbol.gleam @@ -0,0 +1,235 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type WaypointTraitSymbol { + Uncharted + UnderConstruction + Marketplace + Shipyard + Outpost + ScatteredSettlements + SprawlingCities + MegaStructures + PirateBase + Overcrowded + HighTech + Corrupt + Bureaucratic + TradingHub + Industrial + BlackMarket + ResearchFacility + MilitaryBase + SurveillanceOutpost + ExplorationOutpost + MineralDeposits + CommonMetalDeposits + PreciousMetalDeposits + RareMetalDeposits + MethanePools + IceCrystals + ExplosiveGases + StrongMagnetosphere + VibrantAuroras + SaltFlats + Canyons + PerpetualDaylight + PerpetualOvercast + DrySeabeds + MagmaSeas + Supervolcanoes + AshClouds + VastRuins + MutatedFlora + Terraformed + ExtremeTemperatures + ExtremePressure + DiverseLife + ScarceLife + Fossils + WeakGravity + StrongGravity + CrushingGravity + ToxicAtmosphere + CorrosiveAtmosphere + BreathableAtmosphere + ThinAtmosphere + Jovian + Rocky + Volcanic + Frozen + Swamp + Barren + Temperate + Jungle + Ocean + Radioactive + MicroGravityAnomalies + DebrisCluster + DeepCraters + ShallowCraters + UnstableComposition + HollowedInterior + Stripped +} + +pub fn parse(value: String) -> Result(WaypointTraitSymbol, Nil) { + case value { + "UNCHARTED" -> Ok(Uncharted) + "UNDER_CONSTRUCTION" -> Ok(UnderConstruction) + "MARKETPLACE" -> Ok(Marketplace) + "SHIPYARD" -> Ok(Shipyard) + "OUTPOST" -> Ok(Outpost) + "SCATTERED_SETTLEMENTS" -> Ok(ScatteredSettlements) + "SPRAWLING_CITIES" -> Ok(SprawlingCities) + "MEGA_STRUCTURES" -> Ok(MegaStructures) + "PIRATE_BASE" -> Ok(PirateBase) + "OVERCROWDED" -> Ok(Overcrowded) + "HIGH_TECH" -> Ok(HighTech) + "CORRUPT" -> Ok(Corrupt) + "BUREAUCRATIC" -> Ok(Bureaucratic) + "TRADING_HUB" -> Ok(TradingHub) + "INDUSTRIAL" -> Ok(Industrial) + "BLACK_MARKET" -> Ok(BlackMarket) + "RESEARCH_FACILITY" -> Ok(ResearchFacility) + "MILITARY_BASE" -> Ok(MilitaryBase) + "SURVEILLANCE_OUTPOST" -> Ok(SurveillanceOutpost) + "EXPLORATION_OUTPOST" -> Ok(ExplorationOutpost) + "MINERAL_DEPOSITS" -> Ok(MineralDeposits) + "COMMON_METAL_DEPOSITS" -> Ok(CommonMetalDeposits) + "PRECIOUS_METAL_DEPOSITS" -> Ok(PreciousMetalDeposits) + "RARE_METAL_DEPOSITS" -> Ok(RareMetalDeposits) + "METHANE_POOLS" -> Ok(MethanePools) + "ICE_CRYSTALS" -> Ok(IceCrystals) + "EXPLOSIVE_GASES" -> Ok(ExplosiveGases) + "STRONG_MAGNETOSPHERE" -> Ok(StrongMagnetosphere) + "VIBRANT_AURORAS" -> Ok(VibrantAuroras) + "SALT_FLATS" -> Ok(SaltFlats) + "CANYONS" -> Ok(Canyons) + "PERPETUAL_DAYLIGHT" -> Ok(PerpetualDaylight) + "PERPETUAL_OVERCAST" -> Ok(PerpetualOvercast) + "DRY_SEABEDS" -> Ok(DrySeabeds) + "MAGMA_SEAS" -> Ok(MagmaSeas) + "SUPERVOLCANOES" -> Ok(Supervolcanoes) + "ASH_CLOUDS" -> Ok(AshClouds) + "VAST_RUINS" -> Ok(VastRuins) + "MUTATED_FLORA" -> Ok(MutatedFlora) + "TERRAFORMED" -> Ok(Terraformed) + "EXTREME_TEMPERATURES" -> Ok(ExtremeTemperatures) + "EXTREME_PRESSURE" -> Ok(ExtremePressure) + "DIVERSE_LIFE" -> Ok(DiverseLife) + "SCARCE_LIFE" -> Ok(ScarceLife) + "FOSSILS" -> Ok(Fossils) + "WEAK_GRAVITY" -> Ok(WeakGravity) + "STRONG_GRAVITY" -> Ok(StrongGravity) + "CRUSHING_GRAVITY" -> Ok(CrushingGravity) + "TOXIC_ATMOSPHERE" -> Ok(ToxicAtmosphere) + "CORROSIVE_ATMOSPHERE" -> Ok(CorrosiveAtmosphere) + "BREATHABLE_ATMOSPHERE" -> Ok(BreathableAtmosphere) + "THIN_ATMOSPHERE" -> Ok(ThinAtmosphere) + "JOVIAN" -> Ok(Jovian) + "ROCKY" -> Ok(Rocky) + "VOLCANIC" -> Ok(Volcanic) + "FROZEN" -> Ok(Frozen) + "SWAMP" -> Ok(Swamp) + "BARREN" -> Ok(Barren) + "TEMPERATE" -> Ok(Temperate) + "JUNGLE" -> Ok(Jungle) + "OCEAN" -> Ok(Ocean) + "RADIOACTIVE" -> Ok(Radioactive) + "MICRO_GRAVITY_ANOMALIES" -> Ok(MicroGravityAnomalies) + "DEBRIS_CLUSTER" -> Ok(DebrisCluster) + "DEEP_CRATERS" -> Ok(DeepCraters) + "SHALLOW_CRATERS" -> Ok(ShallowCraters) + "UNSTABLE_COMPOSITION" -> Ok(UnstableComposition) + "HOLLOWED_INTERIOR" -> Ok(HollowedInterior) + "STRIPPED" -> Ok(Stripped) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(WaypointTraitSymbol) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(waypoint_trait_symbol) -> decode.success(waypoint_trait_symbol) + Error(Nil) -> decode.failure(Uncharted, "WaypointTraitSymbol") + } +} + +pub fn to_string(waypoint_trait_symbol: WaypointTraitSymbol) -> String { + case waypoint_trait_symbol { + Uncharted -> "UNCHARTED" + UnderConstruction -> "UNDER_CONSTRUCTION" + Marketplace -> "MARKETPLACE" + Shipyard -> "SHIPYARD" + Outpost -> "OUTPOST" + ScatteredSettlements -> "SCATTERED_SETTLEMENTS" + SprawlingCities -> "SPRAWLING_CITIES" + MegaStructures -> "MEGA_STRUCTURES" + PirateBase -> "PIRATE_BASE" + Overcrowded -> "OVERCROWDED" + HighTech -> "HIGH_TECH" + Corrupt -> "CORRUPT" + Bureaucratic -> "BUREAUCRATIC" + TradingHub -> "TRADING_HUB" + Industrial -> "INDUSTRIAL" + BlackMarket -> "BLACK_MARKET" + ResearchFacility -> "RESEARCH_FACILITY" + MilitaryBase -> "MILITARY_BASE" + SurveillanceOutpost -> "SURVEILLANCE_OUTPOST" + ExplorationOutpost -> "EXPLORATION_OUTPOST" + MineralDeposits -> "MINERAL_DEPOSITS" + CommonMetalDeposits -> "COMMON_METAL_DEPOSITS" + PreciousMetalDeposits -> "PRECIOUS_METAL_DEPOSITS" + RareMetalDeposits -> "RARE_METAL_DEPOSITS" + MethanePools -> "METHANE_POOLS" + IceCrystals -> "ICE_CRYSTALS" + ExplosiveGases -> "EXPLOSIVE_GASES" + StrongMagnetosphere -> "STRONG_MAGNETOSPHERE" + VibrantAuroras -> "VIBRANT_AURORAS" + SaltFlats -> "SALT_FLATS" + Canyons -> "CANYONS" + PerpetualDaylight -> "PERPETUAL_DAYLIGHT" + PerpetualOvercast -> "PERPETUAL_OVERCAST" + DrySeabeds -> "DRY_SEABEDS" + MagmaSeas -> "MAGMA_SEAS" + Supervolcanoes -> "SUPERVOLCANOES" + AshClouds -> "ASH_CLOUDS" + VastRuins -> "VAST_RUINS" + MutatedFlora -> "MUTATED_FLORA" + Terraformed -> "TERRAFORMED" + ExtremeTemperatures -> "EXTREME_TEMPERATURES" + ExtremePressure -> "EXTREME_PRESSURE" + DiverseLife -> "DIVERSE_LIFE" + ScarceLife -> "SCARCE_LIFE" + Fossils -> "FOSSILS" + WeakGravity -> "WEAK_GRAVITY" + StrongGravity -> "STRONG_GRAVITY" + CrushingGravity -> "CRUSHING_GRAVITY" + ToxicAtmosphere -> "TOXIC_ATMOSPHERE" + CorrosiveAtmosphere -> "CORROSIVE_ATMOSPHERE" + BreathableAtmosphere -> "BREATHABLE_ATMOSPHERE" + ThinAtmosphere -> "THIN_ATMOSPHERE" + Jovian -> "JOVIAN" + Rocky -> "ROCKY" + Volcanic -> "VOLCANIC" + Frozen -> "FROZEN" + Swamp -> "SWAMP" + Barren -> "BARREN" + Temperate -> "TEMPERATE" + Jungle -> "JUNGLE" + Ocean -> "OCEAN" + Radioactive -> "RADIOACTIVE" + MicroGravityAnomalies -> "MICRO_GRAVITY_ANOMALIES" + DebrisCluster -> "DEBRIS_CLUSTER" + DeepCraters -> "DEEP_CRATERS" + ShallowCraters -> "SHALLOW_CRATERS" + UnstableComposition -> "UNSTABLE_COMPOSITION" + HollowedInterior -> "HOLLOWED_INTERIOR" + Stripped -> "STRIPPED" + } +} + +pub fn encode(waypoint_trait_symbol: WaypointTraitSymbol) -> Json { + json.string(to_string(waypoint_trait_symbol)) +} diff --git a/src/spacetraders_models/waypoint_type.gleam b/src/spacetraders_models/waypoint_type.gleam new file mode 100644 index 0000000..4d9143c --- /dev/null +++ b/src/spacetraders_models/waypoint_type.gleam @@ -0,0 +1,70 @@ +import gleam/dynamic/decode.{type Decoder} +import gleam/json.{type Json} + +pub type WaypointType { + Planet + GasGiant + Moon + OrbitalStation + JumpGate + AsteroidField + Asteroid + EngineeredAsteroid + AsteroidBase + Nebula + DebrisField + GravityWell + ArtificialGravityWell + FuelStation +} + +pub fn parse(value: String) -> Result(WaypointType, Nil) { + case value { + "PLANET" -> Ok(Planet) + "GAS_GIANT" -> Ok(GasGiant) + "MOON" -> Ok(Moon) + "ORBITAL_STATION" -> Ok(OrbitalStation) + "JUMP_GATE" -> Ok(JumpGate) + "ASTEROID_FIELD" -> Ok(AsteroidField) + "ASTEROID" -> Ok(Asteroid) + "ENGINEERED_ASTEROID" -> Ok(EngineeredAsteroid) + "ASTEROID_BASE" -> Ok(AsteroidBase) + "NEBULA" -> Ok(Nebula) + "DEBRIS_FIELD" -> Ok(DebrisField) + "GRAVITY_WELL" -> Ok(GravityWell) + "ARTIFICIAL_GRAVITY_WELL" -> Ok(ArtificialGravityWell) + "FUEL_STATION" -> Ok(FuelStation) + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(WaypointType) { + use value <- decode.then(decode.string) + case parse(value) { + Ok(waypoint_type) -> decode.success(waypoint_type) + Error(Nil) -> decode.failure(Planet, "WaypointType") + } +} + +pub fn to_string(waypoint_type: WaypointType) -> String { + case waypoint_type { + Planet -> "PLANET" + GasGiant -> "GAS_GIANT" + Moon -> "MOON" + OrbitalStation -> "ORBITAL_STATION" + JumpGate -> "JUMP_GATE" + AsteroidField -> "ASTEROID_FIELD" + Asteroid -> "ASTEROID" + EngineeredAsteroid -> "ENGINEERED_ASTEROID" + AsteroidBase -> "ASTEROID_BASE" + Nebula -> "NEBULA" + DebrisField -> "DEBRIS_FIELD" + GravityWell -> "GRAVITY_WELL" + ArtificialGravityWell -> "ARTIFICIAL_GRAVITY_WELL" + FuelStation -> "FUEL_STATION" + } +} + +pub fn encode(waypoint_type: WaypointType) -> Json { + json.string(to_string(waypoint_type)) +} diff --git a/test/spacetraders_models_test.gleam b/test/spacetraders_models_test.gleam new file mode 100644 index 0000000..902c4da --- /dev/null +++ b/test/spacetraders_models_test.gleam @@ -0,0 +1,5 @@ +import gleeunit + +pub fn main() -> Nil { + gleeunit.main() +}