Initial commit
Some checks are pending
test / test (push) Waiting to run

This commit is contained in:
Lily Rose 2025-07-08 23:03:42 +10:00
commit fab4c9df5d
123 changed files with 5152 additions and 0 deletions

23
.github/workflows/test.yml vendored Normal file
View file

@ -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

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.beam
*.ez
/build
erl_crash.dump

24
README.md Normal file
View file

@ -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 <https://hexdocs.pm/spacetraders_models>.
## Development
```sh
gleam run # Run the project
gleam test # Run the tests
```

15
gleam.toml Normal file
View file

@ -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"

17
manifest.toml Normal file
View file

@ -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" }

View file

@ -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:))
}

View file

@ -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))
}

View file

@ -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
}

View file

@ -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))
}

View file

@ -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:,
))
}

View file

@ -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:))
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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
}

View file

@ -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),
)
}
}

View file

@ -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:))
}

View file

@ -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:,
))
}

View file

@ -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))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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:,
))
}

View file

@ -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:,
))
}

View file

@ -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))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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))
}

View file

@ -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:,
))
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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:,
))
}

View file

@ -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))
}

View file

@ -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:))
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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)
}
}

View file

@ -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 {
<<byte, remaining_bytes: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))
<<byte, remaining_bytes: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: <<byte, remaining_bytes:bits>>,
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 {
<<byte, remaining_bytes: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:,
)
}
<<byte, remaining_bytes:bytes>> 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))
}
}

View file

@ -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:))
}

View file

@ -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:,
))
}

View file

@ -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))
}

View file

@ -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:,
))
}

View file

@ -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:,
))
}

View file

@ -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:))
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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:))
}

View file

@ -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:,
))
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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:))
}

View file

@ -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:,
))
}

View file

@ -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:,
))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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:,
))
}

View file

@ -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:,
))
}

View file

@ -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:,
))
}

View file

@ -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))
}

View file

@ -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:,
))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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:))
}

View file

@ -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))
}

View file

@ -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:,
))
}

View file

@ -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:,
))
}

View file

@ -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:,
))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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:,
))
}

View file

@ -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:,
))
}

View file

@ -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:,
))
}

View file

@ -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:,
))
}

View file

@ -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))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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))
}

View file

@ -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:,
))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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:,
))
}

View file

@ -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:,
))
}

View file

@ -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:))
}

View file

@ -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))
}

View file

@ -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:,
))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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:))
}

View file

@ -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))
}

View file

@ -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)),
])
}

Some files were not shown because too many files have changed in this diff Show more