feat(client): net primitives — DmResponse, NarrateResult, DmTransport, ProxyConfig
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
25
client/scripts/net/dm_response.gd
Normal file
25
client/scripts/net/dm_response.gd
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
class_name DmResponse
|
||||||
|
extends RefCounted
|
||||||
|
## The raw outcome of one HTTP attempt, independent of any game meaning.
|
||||||
|
## `status` is 0 when the transport never reached the server; `body` is the
|
||||||
|
## parsed JSON dict, or null when the body was absent or not JSON.
|
||||||
|
|
||||||
|
var status: int
|
||||||
|
var body # Variant: parsed JSON (Dictionary) or null
|
||||||
|
var transport_ok: bool
|
||||||
|
var error: String
|
||||||
|
|
||||||
|
|
||||||
|
func _init(p_status := 0, p_body = null, p_transport_ok := false, p_error := "") -> void:
|
||||||
|
status = p_status
|
||||||
|
body = p_body
|
||||||
|
transport_ok = p_transport_ok
|
||||||
|
error = p_error
|
||||||
|
|
||||||
|
|
||||||
|
static func ok(p_status: int, p_body) -> DmResponse:
|
||||||
|
return DmResponse.new(p_status, p_body, true, "")
|
||||||
|
|
||||||
|
|
||||||
|
static func failed(p_error: String) -> DmResponse:
|
||||||
|
return DmResponse.new(0, null, false, p_error)
|
||||||
1
client/scripts/net/dm_response.gd.uid
Normal file
1
client/scripts/net/dm_response.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bdy2347pcqm4v
|
||||||
10
client/scripts/net/dm_transport.gd
Normal file
10
client/scripts/net/dm_transport.gd
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
class_name DmTransport
|
||||||
|
extends RefCounted
|
||||||
|
## The network seam. DmService talks to this, never to HTTPRequest directly, so
|
||||||
|
## the loop logic is testable with an injected fake. Concrete transports
|
||||||
|
## override post_json. The base returns a failed response (rather than assert())
|
||||||
|
## so a missing override is a loud, catchable failure in tests.
|
||||||
|
|
||||||
|
func post_json(_path: String, _body: Dictionary) -> DmResponse:
|
||||||
|
push_error("DmTransport.post_json must be overridden")
|
||||||
|
return DmResponse.failed("transport not implemented")
|
||||||
1
client/scripts/net/dm_transport.gd.uid
Normal file
1
client/scripts/net/dm_transport.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://crgmujan6vj2l
|
||||||
18
client/scripts/net/narrate_result.gd
Normal file
18
client/scripts/net/narrate_result.gd
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
class_name NarrateResult
|
||||||
|
extends RefCounted
|
||||||
|
## The game-meaningful result of one narrate call. `facts` are the [FACT:]
|
||||||
|
## strings the caller applies to the canon log; empty when degraded.
|
||||||
|
|
||||||
|
var display_text: String
|
||||||
|
var facts: Array
|
||||||
|
var degraded: bool
|
||||||
|
|
||||||
|
|
||||||
|
func _init(p_display_text := "", p_facts := [], p_degraded := false) -> void:
|
||||||
|
display_text = p_display_text
|
||||||
|
facts = p_facts
|
||||||
|
degraded = p_degraded
|
||||||
|
|
||||||
|
|
||||||
|
static func fallback(line: String) -> NarrateResult:
|
||||||
|
return NarrateResult.new(line, [], true)
|
||||||
1
client/scripts/net/narrate_result.gd.uid
Normal file
1
client/scripts/net/narrate_result.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dye0nd5h05cks
|
||||||
14
client/scripts/net/proxy_config.gd
Normal file
14
client/scripts/net/proxy_config.gd
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
class_name ProxyConfig
|
||||||
|
extends RefCounted
|
||||||
|
## The client's one piece of network config: the proxy base URL. Never a key,
|
||||||
|
## never a model name (charter §4). Overridable per environment via project
|
||||||
|
## settings; defaults to the dev proxy.
|
||||||
|
|
||||||
|
const SETTING := "coc_rpg/proxy_base_url"
|
||||||
|
const DEFAULT := "http://localhost:8000"
|
||||||
|
|
||||||
|
|
||||||
|
static func base_url() -> String:
|
||||||
|
var v = ProjectSettings.get_setting(SETTING, DEFAULT)
|
||||||
|
var s := str(v)
|
||||||
|
return s if s != "" else DEFAULT
|
||||||
1
client/scripts/net/proxy_config.gd.uid
Normal file
1
client/scripts/net/proxy_config.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://n7p287j05isf
|
||||||
54
client/tests/unit/test_net_primitives.gd
Normal file
54
client/tests/unit/test_net_primitives.gd
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
extends "res://addons/gut/test.gd"
|
||||||
|
|
||||||
|
const DmResponse = preload("res://scripts/net/dm_response.gd")
|
||||||
|
const NarrateResult = preload("res://scripts/net/narrate_result.gd")
|
||||||
|
const DmTransport = preload("res://scripts/net/dm_transport.gd")
|
||||||
|
const ProxyConfig = preload("res://scripts/net/proxy_config.gd")
|
||||||
|
|
||||||
|
|
||||||
|
func test_dm_response_ok():
|
||||||
|
var r = DmResponse.ok(200, {"prose": "hi"})
|
||||||
|
assert_eq(r.status, 200)
|
||||||
|
assert_true(r.transport_ok)
|
||||||
|
assert_eq(r.body, {"prose": "hi"})
|
||||||
|
assert_eq(r.error, "")
|
||||||
|
|
||||||
|
|
||||||
|
func test_dm_response_failed():
|
||||||
|
var r = DmResponse.failed("boom")
|
||||||
|
assert_false(r.transport_ok)
|
||||||
|
assert_eq(r.status, 0)
|
||||||
|
assert_eq(r.body, null)
|
||||||
|
assert_eq(r.error, "boom")
|
||||||
|
|
||||||
|
|
||||||
|
func test_narrate_result_basic():
|
||||||
|
var r = NarrateResult.new("text", ["a"], false)
|
||||||
|
assert_eq(r.display_text, "text")
|
||||||
|
assert_eq(r.facts, ["a"])
|
||||||
|
assert_false(r.degraded)
|
||||||
|
|
||||||
|
|
||||||
|
func test_narrate_result_fallback():
|
||||||
|
var r = NarrateResult.fallback("cold chamber")
|
||||||
|
assert_eq(r.display_text, "cold chamber")
|
||||||
|
assert_eq(r.facts, [])
|
||||||
|
assert_true(r.degraded)
|
||||||
|
|
||||||
|
|
||||||
|
func test_transport_base_returns_not_implemented():
|
||||||
|
var t = DmTransport.new()
|
||||||
|
var r = await t.post_json("/x", {})
|
||||||
|
assert_false(r.transport_ok)
|
||||||
|
assert_eq(r.error, "transport not implemented")
|
||||||
|
|
||||||
|
|
||||||
|
func test_proxy_config_default():
|
||||||
|
ProjectSettings.set_setting(ProxyConfig.SETTING, null)
|
||||||
|
assert_eq(ProxyConfig.base_url(), ProxyConfig.DEFAULT)
|
||||||
|
|
||||||
|
|
||||||
|
func test_proxy_config_override():
|
||||||
|
ProjectSettings.set_setting(ProxyConfig.SETTING, "http://example.test:9000")
|
||||||
|
assert_eq(ProxyConfig.base_url(), "http://example.test:9000")
|
||||||
|
ProjectSettings.set_setting(ProxyConfig.SETTING, null)
|
||||||
1
client/tests/unit/test_net_primitives.gd.uid
Normal file
1
client/tests/unit/test_net_primitives.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cpihryriwysay
|
||||||
Reference in New Issue
Block a user