Compare commits
3 commits
Author | SHA1 | Date | |
---|---|---|---|
4069651171 | |||
02735d1744 | |||
520b5d380c |
21 changed files with 199 additions and 283 deletions
9
LICENSE.md
Normal file
9
LICENSE.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2025 Lily Rose
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,5 +1,5 @@
|
|||
name = "spacetraders_models"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
gleam = ">= 1.11.0"
|
||||
description = "Gleam types for the spacetraders.io game API"
|
||||
licences = ["MIT"]
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
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
|
||||
import spacetraders_models/timestamp.{type Timestamp}
|
||||
|
||||
pub type Account {
|
||||
Account(
|
||||
|
@ -25,6 +24,6 @@ pub fn decoder() -> Decoder(Account) {
|
|||
option.None,
|
||||
decode.optional(decode.string),
|
||||
)
|
||||
use created_at <- decode.field("createdAt", time.rfc3339_timestamp_decoder())
|
||||
use created_at <- decode.field("createdAt", timestamp.decoder())
|
||||
decode.success(Account(id:, email:, token:, created_at:))
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
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
|
||||
import spacetraders_models/timestamp.{type Timestamp}
|
||||
|
||||
pub type AgentEvent {
|
||||
AgentEvent(
|
||||
|
@ -24,6 +23,6 @@ pub fn decoder() -> Decoder(AgentEvent) {
|
|||
option.None,
|
||||
decode.optional(decode.dynamic),
|
||||
)
|
||||
use created_at <- decode.field("createdAt", time.rfc3339_timestamp_decoder())
|
||||
use created_at <- decode.field("createdAt", timestamp.decoder())
|
||||
decode.success(AgentEvent(id:, type_:, message:, data:, created_at:))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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/timestamp.{type Timestamp}
|
||||
import spacetraders_models/waypoint_symbol.{type WaypointSymbol}
|
||||
|
||||
pub type Chart {
|
||||
|
@ -18,9 +17,6 @@ pub fn decoder() -> Decoder(Chart) {
|
|||
waypoint_symbol.decoder(),
|
||||
)
|
||||
use submitted_by <- decode.field("submittedBy", agent_symbol.decoder())
|
||||
use submitted_on <- decode.field(
|
||||
"submittedOn",
|
||||
time.rfc3339_timestamp_decoder(),
|
||||
)
|
||||
use submitted_on <- decode.field("submittedOn", timestamp.decoder())
|
||||
decode.success(Chart(waypoint_symbol:, submitted_by:, submitted_on:))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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/timestamp.{type Timestamp}
|
||||
import spacetraders_models/waypoint_symbol.{type WaypointSymbol}
|
||||
|
||||
pub type ChartTransaction {
|
||||
|
@ -20,7 +19,7 @@ pub fn decoder() -> Decoder(ChartTransaction) {
|
|||
)
|
||||
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())
|
||||
use timestamp <- decode.field("timestamp", timestamp.decoder())
|
||||
decode.success(ChartTransaction(
|
||||
waypoint_symbol:,
|
||||
ship_symbol:,
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
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
|
||||
import spacetraders_models/timestamp.{type Timestamp}
|
||||
|
||||
pub type Contract {
|
||||
Contract(
|
||||
|
@ -29,7 +28,7 @@ pub fn decoder() -> Decoder(Contract) {
|
|||
use deadline_to_accept <- decode.optional_field(
|
||||
"deadlineToAccept",
|
||||
option.None,
|
||||
decode.optional(time.rfc3339_timestamp_decoder()),
|
||||
decode.optional(timestamp.decoder()),
|
||||
)
|
||||
decode.success(Contract(
|
||||
id:,
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
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
|
||||
import spacetraders_models/timestamp.{type Timestamp}
|
||||
|
||||
pub type ContractTerms {
|
||||
ContractTerms(
|
||||
|
@ -14,7 +13,7 @@ pub type ContractTerms {
|
|||
}
|
||||
|
||||
pub fn decoder() -> Decoder(ContractTerms) {
|
||||
use deadline <- decode.field("deadline", time.rfc3339_timestamp_decoder())
|
||||
use deadline <- decode.field("deadline", timestamp.decoder())
|
||||
use payment <- decode.field("payment", contract_payment.decoder())
|
||||
use deliver <- decode.optional_field(
|
||||
"deliver",
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
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}
|
||||
import spacetraders_models/timestamp.{type Timestamp}
|
||||
|
||||
pub type Cooldown {
|
||||
Cooldown(
|
||||
|
@ -20,7 +19,7 @@ pub fn decoder() -> Decoder(Cooldown) {
|
|||
use expiration <- decode.optional_field(
|
||||
"expiration",
|
||||
option.None,
|
||||
decode.optional(time.rfc3339_timestamp_decoder()),
|
||||
decode.optional(timestamp.decoder()),
|
||||
)
|
||||
decode.success(Cooldown(
|
||||
ship_symbol:,
|
||||
|
|
125
src/spacetraders_models/date.gleam
Normal file
125
src/spacetraders_models/date.gleam
Normal file
|
@ -0,0 +1,125 @@
|
|||
import gleam/bit_array
|
||||
import gleam/dynamic/decode.{type Decoder}
|
||||
import gleam/int
|
||||
import gleam/json.{type Json}
|
||||
import gleam/pair
|
||||
import gleam/result
|
||||
import gleam/time/calendar.{
|
||||
type Month, April, August, Date, December, February, January, July, June,
|
||||
March, May, November, October, September,
|
||||
}
|
||||
|
||||
pub type Date =
|
||||
calendar.Date
|
||||
|
||||
pub fn parse(value: String) -> Result(Date, Nil) {
|
||||
let bytes = bit_array.from_string(value)
|
||||
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 decoder() -> Decoder(Date) {
|
||||
use value <- decode.then(decode.string)
|
||||
case parse(value) {
|
||||
Ok(date) -> decode.success(date)
|
||||
Error(Nil) -> decode.failure(Date(1970, calendar.January, 1), "Date")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode(date: Date) -> Json {
|
||||
json.string(
|
||||
int.to_string(date.year)
|
||||
<> "-"
|
||||
<> int.to_string(calendar.month_to_int(date.month))
|
||||
<> "-"
|
||||
<> int.to_string(date.day),
|
||||
)
|
||||
}
|
||||
|
||||
const byte_zero: Int = 0x30
|
||||
|
||||
const byte_nine: Int = 0x39
|
||||
|
||||
const byte_minus: Int = 0x2D
|
||||
|
||||
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 }
|
||||
}
|
|
@ -4,9 +4,8 @@ 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
|
||||
import spacetraders_models/date.{type Date}
|
||||
import spacetraders_models/timestamp.{type Timestamp}
|
||||
|
||||
pub type JwtDecodeError {
|
||||
MissingHeader
|
||||
|
@ -91,10 +90,10 @@ fn jwt_payload_decoder() -> Decoder(JwtPayload) {
|
|||
use reset_date <- decode.optional_field(
|
||||
"reset_date",
|
||||
option.None,
|
||||
decode.optional(time.iso8601_date_decoder()),
|
||||
decode.optional(date.decoder()),
|
||||
)
|
||||
use issued_at_int <- decode.field("iat", decode.int)
|
||||
let issued_at = timestamp.from_unix_seconds(issued_at_int)
|
||||
use issued_at_unix <- decode.field("iat", decode.int)
|
||||
let issued_at = timestamp.from_unix(issued_at_unix)
|
||||
use subject <- decode.field("sub", decode.string)
|
||||
decode.success(JwtPayload(
|
||||
identifier:,
|
||||
|
|
|
@ -1,219 +0,0 @@
|
|||
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))
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
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/timestamp.{type Timestamp}
|
||||
import spacetraders_models/trade_symbol.{type TradeSymbol}
|
||||
import spacetraders_models/transaction_type.{type TransactionType}
|
||||
import spacetraders_models/waypoint_symbol.{type WaypointSymbol}
|
||||
|
@ -30,7 +29,7 @@ pub fn decoder() -> Decoder(MarketTransaction) {
|
|||
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())
|
||||
use timestamp <- decode.field("timestamp", timestamp.decoder())
|
||||
decode.success(MarketTransaction(
|
||||
waypoint_symbol:,
|
||||
ship_symbol:,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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/timestamp.{type Timestamp}
|
||||
import spacetraders_models/waypoint_symbol.{type WaypointSymbol}
|
||||
|
||||
pub type RepairTransaction {
|
||||
|
@ -20,7 +19,7 @@ pub fn decoder() -> Decoder(RepairTransaction) {
|
|||
)
|
||||
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())
|
||||
use timestamp <- decode.field("timestamp", timestamp.decoder())
|
||||
decode.success(RepairTransaction(
|
||||
waypoint_symbol:,
|
||||
ship_symbol:,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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/timestamp.{type Timestamp}
|
||||
import spacetraders_models/waypoint_symbol.{type WaypointSymbol}
|
||||
|
||||
pub type ScrapTransaction {
|
||||
|
@ -20,7 +19,7 @@ pub fn decoder() -> Decoder(ScrapTransaction) {
|
|||
)
|
||||
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())
|
||||
use timestamp <- decode.field("timestamp", timestamp.decoder())
|
||||
decode.success(ScrapTransaction(
|
||||
waypoint_symbol:,
|
||||
ship_symbol:,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import gleam/dynamic/decode.{type Decoder}
|
||||
import gleam/time/timestamp.{type Timestamp}
|
||||
import spacetraders_models/internal/time
|
||||
import spacetraders_models/timestamp.{type Timestamp}
|
||||
|
||||
pub type ShipFuelConsumed {
|
||||
ShipFuelConsumed(amount: Int, timestamp: Timestamp)
|
||||
|
@ -8,6 +7,6 @@ pub type ShipFuelConsumed {
|
|||
|
||||
pub fn decoder() -> Decoder(ShipFuelConsumed) {
|
||||
use amount <- decode.field("amount", decode.int)
|
||||
use timestamp <- decode.field("timestamp", time.rfc3339_timestamp_decoder())
|
||||
use timestamp <- decode.field("timestamp", timestamp.decoder())
|
||||
decode.success(ShipFuelConsumed(amount:, timestamp:))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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/timestamp.{type Timestamp}
|
||||
import spacetraders_models/trade_symbol.{type TradeSymbol}
|
||||
import spacetraders_models/waypoint_symbol.{type WaypointSymbol}
|
||||
|
||||
|
@ -23,7 +22,7 @@ pub fn decoder() -> Decoder(ShipModificationTransaction) {
|
|||
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())
|
||||
use timestamp <- decode.field("timestamp", timestamp.decoder())
|
||||
decode.success(ShipModificationTransaction(
|
||||
waypoint_symbol:,
|
||||
ship_symbol:,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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}
|
||||
import spacetraders_models/timestamp.{type Timestamp}
|
||||
|
||||
pub type ShipNavRoute {
|
||||
ShipNavRoute(
|
||||
|
@ -18,10 +17,7 @@ pub fn decoder() -> Decoder(ShipNavRoute) {
|
|||
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())
|
||||
use departure_time <- decode.field("departureTime", timestamp.decoder())
|
||||
use arrival <- decode.field("arrival", timestamp.decoder())
|
||||
decode.success(ShipNavRoute(destination:, origin:, departure_time:, arrival:))
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
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/timestamp.{type Timestamp}
|
||||
import spacetraders_models/waypoint_symbol.{type WaypointSymbol}
|
||||
|
||||
pub type ShipyardTransaction {
|
||||
|
@ -23,7 +22,7 @@ pub fn decoder() -> Decoder(ShipyardTransaction) {
|
|||
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())
|
||||
use timestamp <- decode.field("timestamp", timestamp.decoder())
|
||||
decode.success(ShipyardTransaction(
|
||||
waypoint_symbol:,
|
||||
ship_type:,
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
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/timestamp.{type Timestamp}
|
||||
import spacetraders_models/waypoint_symbol.{type WaypointSymbol}
|
||||
|
||||
pub type Survey {
|
||||
|
@ -25,7 +23,7 @@ pub fn decoder() -> Decoder(Survey) {
|
|||
"deposits",
|
||||
decode.list(survey_deposit.decoder()),
|
||||
)
|
||||
use expiration <- decode.field("expiration", time.rfc3339_timestamp_decoder())
|
||||
use expiration <- decode.field("expiration", timestamp.decoder())
|
||||
use size <- decode.field("size", survey_size.decoder())
|
||||
decode.success(Survey(signature:, symbol:, deposits:, expiration:, size:))
|
||||
}
|
||||
|
@ -35,10 +33,7 @@ pub fn encode(survey: Survey) -> Json {
|
|||
#("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)),
|
||||
),
|
||||
#("expiration", timestamp.encode(survey.expiration)),
|
||||
#("size", survey_size.encode(survey.size)),
|
||||
])
|
||||
}
|
||||
|
|
27
src/spacetraders_models/timestamp.gleam
Normal file
27
src/spacetraders_models/timestamp.gleam
Normal file
|
@ -0,0 +1,27 @@
|
|||
import gleam/dynamic/decode.{type Decoder}
|
||||
import gleam/json.{type Json}
|
||||
import gleam/time/calendar
|
||||
import gleam/time/timestamp
|
||||
|
||||
pub type Timestamp =
|
||||
timestamp.Timestamp
|
||||
|
||||
pub fn parse(value: String) -> Result(Timestamp, Nil) {
|
||||
timestamp.parse_rfc3339(value)
|
||||
}
|
||||
|
||||
pub fn from_unix(seconds: Int) -> Timestamp {
|
||||
timestamp.from_unix_seconds(seconds)
|
||||
}
|
||||
|
||||
pub fn decoder() -> Decoder(Timestamp) {
|
||||
use value <- decode.then(decode.string)
|
||||
case parse(value) {
|
||||
Ok(timestamp) -> decode.success(timestamp)
|
||||
Error(Nil) -> decode.failure(timestamp.from_unix_seconds(0), "Timestamp")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode(timestamp: Timestamp) -> Json {
|
||||
json.string(timestamp.to_rfc3339(timestamp, calendar.utc_offset))
|
||||
}
|
Loading…
Reference in a new issue