hyf

Context-aware query service for Radroots
git clone https://radroots.dev/git/hyf.git
Log | Files | Refs | README | LICENSE

jev_client.mojo (3776B)


      1 from flare.http import HttpClient
      2 from flare.tls import TlsConfig, TlsVerify
      3 from json import Value, dumps
      4 from hyf_provider.jev_failures import map_jev_failure
      5 from hyf_provider.jev_retry import RetryPolicy, should_retry
      6 
      7 
      8 @fieldwise_init
      9 struct JevHttpOutcome(Copyable, Movable):
     10     var status: Int
     11     var body_text: String
     12 
     13 
     14 def _is_loopback_host(host: String) -> Bool:
     15     return (
     16         host == "localhost"
     17         or host == "127.0.0.1"
     18         or host == "[::1]"
     19         or host == "::1"
     20     )
     21 
     22 
     23 def validate_jev_base_url(base_url: String) raises -> String:
     24     var trimmed = String(String(base_url).strip())
     25     while trimmed.byte_length() > 1 and trimmed.endswith("/"):
     26         trimmed = String(trimmed[byte = 0 : trimmed.byte_length() - 1])
     27     if trimmed == "":
     28         raise Error("jev base_url must not be empty")
     29     var scheme = ""
     30     var rest = trimmed
     31     if trimmed.startswith("https://"):
     32         scheme = "https"
     33         rest = String(trimmed[byte=8:])
     34     elif trimmed.startswith("http://"):
     35         scheme = "http"
     36         rest = String(trimmed[byte=7:])
     37     else:
     38         raise Error("jev base_url must use https")
     39     if rest.find("@") >= 0:
     40         raise Error("jev base_url must not contain credentials")
     41     if rest.find("?") >= 0:
     42         raise Error("jev base_url must not contain a query")
     43     if rest.find("#") >= 0:
     44         raise Error("jev base_url must not contain a fragment")
     45     var slash = rest.find("/")
     46     var host_port = rest if slash < 0 else String(rest[byte=0:slash])
     47     var host = host_port
     48     var colon = host_port.find(":")
     49     if colon >= 0:
     50         if host_port.startswith("["):
     51             var close = host_port.find("]")
     52             if close >= 0:
     53                 host = String(host_port[byte = 0 : close + 1])
     54         else:
     55             host = String(host_port[byte=0:colon])
     56     if host == "":
     57         raise Error("jev base_url must include a host")
     58     if scheme == "http" and not _is_loopback_host(host):
     59         raise Error("jev base_url must use https unless the host is loopback")
     60     return trimmed^
     61 
     62 
     63 def jev_systemone_url(base_url: String) raises -> String:
     64     return validate_jev_base_url(base_url) + "/v1/systemone"
     65 
     66 
     67 def post_jev_systemone(
     68     base_url: String, body: Value, timeout_ms: Int
     69 ) raises -> JevHttpOutcome:
     70     var url = jev_systemone_url(base_url)
     71     with HttpClient(timeout_ms=timeout_ms, max_redirects=0) as client:
     72         var response = client.post(url, dumps(body))
     73         return JevHttpOutcome(status=response.status, body_text=response.text())
     74 
     75 
     76 def production_tls_config() -> TlsConfig:
     77     return TlsConfig()
     78 
     79 
     80 def assert_tls_verification_required(config: TlsConfig) raises:
     81     if config.verify != TlsVerify.REQUIRED:
     82         raise Error("external provider transport must verify TLS certificates")
     83 
     84 
     85 def redirects_forward_credentials() -> Bool:
     86     return False
     87 
     88 
     89 def failure_kind_for_status(status: Int) -> String:
     90     if status == 401:
     91         return "authentication"
     92     if status == 400 or status == 422 or status == 404:
     93         return "validation"
     94     if status == 408:
     95         return "request_timeout"
     96     if status == 429:
     97         return "rate_limit"
     98     if status == 529:
     99         return "overloaded"
    100     if status == 500 or status == 502 or status == 503 or status == 504:
    101         return "internal_server"
    102     return "unexpected_status"
    103 
    104 
    105 def retry_decision(
    106     policy: RetryPolicy, attempt: Int, elapsed_ms: Int, status: Int
    107 ) raises -> Bool:
    108     if status >= 200 and status < 300:
    109         raise Error("retry_decision requires a failure status")
    110     var kind = failure_kind_for_status(status)
    111     var failure = map_jev_failure(
    112         kind if kind != "unexpected_status" else "transport"
    113     )
    114     return should_retry(policy, attempt, elapsed_ms, failure.retryable)