additional changes
This commit is contained in:
@@ -9,3 +9,4 @@ infra/desi-explorer-infra
|
|||||||
api/target/
|
api/target/
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
*.env
|
||||||
|
|||||||
@@ -4,6 +4,20 @@
|
|||||||
# `api-*` convenience targets).
|
# `api-*` convenience targets).
|
||||||
|
|
||||||
CARGO ?= cargo
|
CARGO ?= cargo
|
||||||
|
ROOT := ..
|
||||||
|
|
||||||
|
# Normalize API_ENV_FILE / API_DESI_DATA (given relative to the repo root) to
|
||||||
|
# absolute paths so the API process can open them regardless of its working
|
||||||
|
# directory. "override" is required because they are usually passed as
|
||||||
|
# command-line/env vars, which would otherwise override any assignment here.
|
||||||
|
define normalize_path
|
||||||
|
ifdef $1
|
||||||
|
ifneq ($(abspath $($1)),$($1))
|
||||||
|
override $1 := $(abspath $(ROOT)/$($1))
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
endef
|
||||||
|
$(foreach v,API_ENV_FILE API_DESI_DATA,$(eval $(call normalize_path,$v)))
|
||||||
|
|
||||||
.PHONY: help setup run build test check fmt clean
|
.PHONY: help setup run build test check fmt clean
|
||||||
|
|
||||||
|
|||||||
@@ -55,56 +55,3 @@ impl CatalogStore {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn load_parses_catalogs_and_objects() {
|
|
||||||
let json = r#"{
|
|
||||||
"catalogs": [
|
|
||||||
{"name":"edr","release":"EDR","description":"test","object_count":2}
|
|
||||||
],
|
|
||||||
"objects": [
|
|
||||||
{"id":"o1","catalog":"edr","object_type":"GALAXY","ra":1.5,"dec":2.5,"redshift":0.8}
|
|
||||||
]
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let dir = std::env::temp_dir().join(format!(
|
|
||||||
"desi_explorer_store_{}_{}",
|
|
||||||
std::process::id(),
|
|
||||||
line!()
|
|
||||||
));
|
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
|
||||||
let path = dir.join("data.json");
|
|
||||||
std::fs::write(&path, json).unwrap();
|
|
||||||
|
|
||||||
let store = CatalogStore::load(&path).unwrap();
|
|
||||||
let _ = std::fs::remove_dir_all(&dir);
|
|
||||||
|
|
||||||
assert_eq!(store.catalogs.len(), 1);
|
|
||||||
assert_eq!(store.catalogs[0].name, "edr");
|
|
||||||
assert_eq!(store.catalogs[0].object_count, Some(2));
|
|
||||||
assert_eq!(store.objects.len(), 1);
|
|
||||||
assert_eq!(store.objects[0].id, "o1");
|
|
||||||
assert_eq!(store.objects[0].ra, 1.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn load_rejects_malformed_json() {
|
|
||||||
let dir = std::env::temp_dir().join(format!(
|
|
||||||
"desi_explorer_store_{}_{}",
|
|
||||||
std::process::id(),
|
|
||||||
line!()
|
|
||||||
));
|
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
|
||||||
let path = dir.join("data.json");
|
|
||||||
std::fs::write(&path, "not json").unwrap();
|
|
||||||
|
|
||||||
let result = CatalogStore::load(&path);
|
|
||||||
let _ = std::fs::remove_dir_all(&dir);
|
|
||||||
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
use desi_explorer_api::store::CatalogStore;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_parses_catalogs_and_objects() {
|
||||||
|
let json = r#"{
|
||||||
|
"catalogs": [
|
||||||
|
{"name":"edr","release":"EDR","description":"test","object_count":2}
|
||||||
|
],
|
||||||
|
"objects": [
|
||||||
|
{"id":"o1","catalog":"edr","object_type":"GALAXY","ra":1.5,"dec":2.5,"redshift":0.8}
|
||||||
|
]
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let dir = std::env::temp_dir().join(format!(
|
||||||
|
"desi_explorer_store_{}_{}",
|
||||||
|
std::process::id(),
|
||||||
|
line!()
|
||||||
|
));
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
let path = dir.join("data.json");
|
||||||
|
std::fs::write(&path, json).unwrap();
|
||||||
|
|
||||||
|
let store = CatalogStore::load(&path).unwrap();
|
||||||
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
|
|
||||||
|
assert_eq!(store.catalogs.len(), 1);
|
||||||
|
assert_eq!(store.catalogs[0].name, "edr");
|
||||||
|
assert_eq!(store.catalogs[0].object_count, Some(2));
|
||||||
|
assert_eq!(store.objects.len(), 1);
|
||||||
|
assert_eq!(store.objects[0].id, "o1");
|
||||||
|
assert_eq!(store.objects[0].ra, 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_rejects_malformed_json() {
|
||||||
|
let dir = std::env::temp_dir().join(format!(
|
||||||
|
"desi_explorer_store_{}_{}",
|
||||||
|
std::process::id(),
|
||||||
|
line!()
|
||||||
|
));
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
let path = dir.join("data.json");
|
||||||
|
std::fs::write(&path, "not json").unwrap();
|
||||||
|
|
||||||
|
let result = CatalogStore::load(&path);
|
||||||
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-1
@@ -8,13 +8,28 @@
|
|||||||
# output dirs live at the repo root and are referenced through `$(ROOT)`.
|
# output dirs live at the repo root and are referenced through `$(ROOT)`.
|
||||||
|
|
||||||
ODIN ?= odin
|
ODIN ?= odin
|
||||||
|
GDB ?= gdb
|
||||||
ROOT := ..
|
ROOT := ..
|
||||||
BIN := $(ROOT)/bin
|
BIN := $(ROOT)/bin
|
||||||
BINARY := $(BIN)/desi_explorer
|
BINARY := $(BIN)/desi_explorer
|
||||||
ODIN_FLAGS := -collection:lib=lib/local
|
ODIN_FLAGS := -collection:lib=lib/local
|
||||||
WASM_DEFINE := RAYLIB_WASM_LIB=env.o
|
WASM_DEFINE := RAYLIB_WASM_LIB=env.o
|
||||||
|
|
||||||
.PHONY: help setup add-dep run build build-debug build-web test clean fmt
|
# Normalize GUI_ENV_FILE (given relative to the repo root) to an absolute path
|
||||||
|
# so the Odin process can open it regardless of its working directory.
|
||||||
|
# "override" is required because GUI_ENV_FILE is usually set on the command
|
||||||
|
# line (or passed as an env var to this sub-make), which would otherwise
|
||||||
|
# override any assignment made here.
|
||||||
|
ifdef GUI_ENV_FILE
|
||||||
|
ifneq ($(abspath $(GUI_ENV_FILE)),$(GUI_ENV_FILE))
|
||||||
|
override GUI_ENV_FILE := $(abspath $(ROOT)/$(GUI_ENV_FILE))
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
# "override" on a command-line variable silently drops it from the recipe
|
||||||
|
# environment; re-export it so the app can find its env file (run + gdb).
|
||||||
|
export GUI_ENV_FILE
|
||||||
|
|
||||||
|
.PHONY: help setup add-dep run build build-debug gdb build-web test clean fmt
|
||||||
|
|
||||||
help: ## List available targets
|
help: ## List available targets
|
||||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||||
@@ -41,6 +56,9 @@ build-debug: ## Debug build -> bin/desi_explorer
|
|||||||
@mkdir -p lib/local $(BIN)
|
@mkdir -p lib/local $(BIN)
|
||||||
$(ODIN) build src $(ODIN_FLAGS) -o:none -debug -out:$(BINARY)
|
$(ODIN) build src $(ODIN_FLAGS) -o:none -debug -out:$(BINARY)
|
||||||
|
|
||||||
|
gdb: build-debug ## Run the native app under gdb (type 'run', then 'bt' on a crash)
|
||||||
|
$(GDB) -q --args $(BINARY) $(ARGS)
|
||||||
|
|
||||||
build-web: ## WebAssembly build -> build/web (needs emscripten)
|
build-web: ## WebAssembly build -> build/web (needs emscripten)
|
||||||
@scripts/build_web.sh
|
@scripts/build_web.sh
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,7 @@ import "core:strings"
|
|||||||
// double quotes. Real process environment variables take precedence over the
|
// double quotes. Real process environment variables take precedence over the
|
||||||
// file. The returned map owns its keys/values; release it with destroy.
|
// file. The returned map owns its keys/values; release it with destroy.
|
||||||
@(private)
|
@(private)
|
||||||
parse :: proc(
|
parse :: proc(src: string, allocator := context.allocator) -> map[string]string {
|
||||||
src: string,
|
|
||||||
allocator := context.allocator,
|
|
||||||
) -> map[string]string {
|
|
||||||
result := make(map[string]string, allocator)
|
result := make(map[string]string, allocator)
|
||||||
|
|
||||||
it := src
|
it := src
|
||||||
@@ -54,18 +51,12 @@ parse :: proc(
|
|||||||
|
|
||||||
// parse_file reads a dotenv file from disk and parses it into a map. It
|
// parse_file reads a dotenv file from disk and parses it into a map. It
|
||||||
// returns (nil, false) when the file cannot be read (e.g. it does not exist).
|
// returns (nil, false) when the file cannot be read (e.g. it does not exist).
|
||||||
parse_file :: proc(
|
parse_file :: proc(filename: string, allocator := context.allocator) -> (map[string]string, bool) {
|
||||||
filename: string,
|
|
||||||
allocator := context.allocator,
|
|
||||||
) -> (
|
|
||||||
map[string]string,
|
|
||||||
bool,
|
|
||||||
) {
|
|
||||||
data, err := os.read_entire_file(filename, allocator)
|
data, err := os.read_entire_file(filename, allocator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
defer delete(data)
|
defer delete(data, allocator)
|
||||||
|
|
||||||
return parse(string(data), allocator), true
|
return parse(string(data), allocator), true
|
||||||
}
|
}
|
||||||
@@ -86,8 +77,7 @@ destroy :: proc(env: map[string]string) {
|
|||||||
// case-insensitive, so API_URL maps onto api_url (or an `env:"API_URL"` tag).
|
// case-insensitive, so API_URL maps onto api_url (or an `env:"API_URL"` tag).
|
||||||
@(private)
|
@(private)
|
||||||
env_key_for_field :: proc(field: reflect.Struct_Field) -> string {
|
env_key_for_field :: proc(field: reflect.Struct_Field) -> string {
|
||||||
if tag_key, ok := reflect.struct_tag_lookup(field.tag, "env");
|
if tag_key, ok := reflect.struct_tag_lookup(field.tag, "env"); ok && tag_key != "" {
|
||||||
ok && tag_key != "" {
|
|
||||||
return tag_key
|
return tag_key
|
||||||
}
|
}
|
||||||
return field.name
|
return field.name
|
||||||
@@ -100,11 +90,7 @@ env_key_for_field :: proc(field: reflect.Struct_Field) -> string {
|
|||||||
// and floats with strconv.parse_f64. Keys missing from env leave the field
|
// and floats with strconv.parse_f64. Keys missing from env leave the field
|
||||||
// at its zero value. It returns false if a present value cannot be converted
|
// at its zero value. It returns false if a present value cannot be converted
|
||||||
// to the field's type.
|
// to the field's type.
|
||||||
decode :: proc(
|
decode :: proc(env: map[string]string, dest: ^$T, allocator := context.allocator) -> bool {
|
||||||
env: map[string]string,
|
|
||||||
dest: ^$T,
|
|
||||||
allocator := context.allocator,
|
|
||||||
) -> bool {
|
|
||||||
ti := reflect.type_info_base(type_info_of(T))
|
ti := reflect.type_info_base(type_info_of(T))
|
||||||
fields, ok := ti.variant.(runtime.Type_Info_Struct)
|
fields, ok := ti.variant.(runtime.Type_Info_Struct)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
+5
-8
@@ -11,10 +11,7 @@ get_config :: proc(env_file: ^string = nil) -> (^Config, ^Error) {
|
|||||||
path := ".env"
|
path := ".env"
|
||||||
if env_file != nil && env_file^ != "" {
|
if env_file != nil && env_file^ != "" {
|
||||||
path = env_file^
|
path = env_file^
|
||||||
} else if from_env, ok := os.lookup_env(
|
} else if from_env, ok := os.lookup_env("GUI_ENV_FILE", context.temp_allocator); ok {
|
||||||
"GUI_ENV_FILE",
|
|
||||||
context.temp_allocator,
|
|
||||||
); ok {
|
|
||||||
path = from_env
|
path = from_env
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,9 +36,9 @@ get_config :: proc(env_file: ^string = nil) -> (^Config, ^Error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_config :: proc(c: ^Config) -> ^Error {
|
validate_config :: proc(c: ^Config) -> (err: ^Error) {
|
||||||
err := new(Error)
|
if c.api_url == "" {
|
||||||
if c.api_url == "" do err.type = .Config; err.message = "'API_URL' is required"
|
err = &Error{.Config, "'API_URL' is required"}
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -1,7 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
import "core:math"
|
import "core:math"
|
||||||
import "core:math/rand"
|
import "core:math/rand"
|
||||||
|
import "core:os"
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
WIDTH :: 1280
|
WIDTH :: 1280
|
||||||
@@ -27,7 +29,9 @@ main :: proc() {
|
|||||||
|
|
||||||
c: ^Config
|
c: ^Config
|
||||||
|
|
||||||
if c, err := get_config(); err != nil {
|
s := os.get_env("GUI_ENV_FILE", context.temp_allocator)
|
||||||
|
|
||||||
|
if c, err := get_config(&s); err != nil {
|
||||||
panic(format_error(err))
|
panic(format_error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user