okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

Aly Raffaufrefactor(cli): reuse generated wire and settings contracts7aba059

main
3.6 KiB158 linesraw
1package daemon
2
3import (
4	"bytes"
5	"encoding/json"
6	"errors"
7	"net/http"
8	"reflect"
9	"strings"
10	"time"
11
12	"albedo/cli/internal/daemon/protocol"
13)
14
15func value[T any](pointer *T) T {
16	if pointer != nil {
17		return *pointer
18	}
19	var zero T
20	return zero
21}
22func timestampMilliseconds(value string) *int64 {
23	parsed, err := time.Parse(time.RFC3339Nano, value)
24	if err != nil {
25		return nil
26	}
27	ms := parsed.UnixMilli()
28	return &ms
29}
30func timestampSeconds(value string) *int64 {
31	parsed, err := time.Parse(time.RFC3339Nano, value)
32	if err != nil {
33		return nil
34	}
35	seconds := parsed.Unix()
36	return &seconds
37}
38
39func intPointer(value *int64) *int {
40	if value == nil {
41		return nil
42	}
43	n := int(*value)
44	return &n
45}
46func observedHeaders(etag string) (http.Header, error) {
47	if etag == "" {
48		return nil, errors.New("this change requires the resource's observed ETag; refresh it first")
49	}
50	return http.Header{"If-Match": {etag}, "Content-Type": {"application/merge-patch+json"}}, nil
51}
52func decodeRequired(data []byte, target any, names ...string) error {
53	data = bytes.TrimSpace(data)
54	if len(data) == 0 || data[0] != '{' {
55		return fieldError("object")
56	}
57	kind := reflect.TypeOf(target).Elem()
58	generated := kind.PkgPath() == reflect.TypeFor[protocol.Session]().PkgPath()
59	if generated || len(names) > 0 {
60		var tree any
61		decoder := json.NewDecoder(bytes.NewReader(data))
62		decoder.UseNumber()
63		if err := decoder.Decode(&tree); err != nil {
64			return err
65		}
66		fields, ok := tree.(map[string]any)
67		if !ok {
68			return fieldError("object")
69		}
70		for _, name := range names {
71			if _, ok := fields[name]; !ok {
72				return fieldError(name)
73			}
74		}
75		if generated {
76			if err := validateWireValue(tree, kind); err != nil {
77				return err
78			}
79		}
80	}
81	return json.Unmarshal(data, target)
82}
83
84// Validate one parsed tree so nested required members and nulls never become
85// Go zero values. Raw unions are decoded by their discriminated adapters.
86func validateWireValue(value any, kind reflect.Type) error {
87	if kind == reflect.TypeFor[json.RawMessage]() {
88		return nil
89	}
90	if kind.Kind() == reflect.Pointer {
91		if value == nil {
92			return nil
93		}
94		return validateWireValue(value, kind.Elem())
95	}
96	if value == nil {
97		return fieldError("null " + kind.String())
98	}
99	switch kind.Kind() {
100	case reflect.Struct:
101		fields, ok := value.(map[string]any)
102		if !ok {
103			return fieldError("object")
104		}
105		for field := range kind.Fields() {
106			name, options, _ := strings.Cut(field.Tag.Get("json"), ",")
107			if name == "" || name == "-" {
108				continue
109			}
110			child, present := fields[name]
111			if !present {
112				if !strings.Contains(options, "omitempty") {
113					return fieldError(name)
114				}
115				continue
116			}
117			if child == nil && field.Type != reflect.TypeFor[json.RawMessage]() {
118				if field.Tag.Get("nullable") != "true" {
119					return fieldError(name)
120				}
121				continue
122			}
123			if err := validateWireValue(child, field.Type); err != nil {
124				return fmtField(name, err)
125			}
126		}
127	case reflect.Slice:
128		items, ok := value.([]any)
129		if !ok {
130			return fieldError("array")
131		}
132		for _, item := range items {
133			if err := validateWireValue(item, kind.Elem()); err != nil {
134				return err
135			}
136		}
137	case reflect.Map:
138		fields, ok := value.(map[string]any)
139		if !ok {
140			return fieldError("object")
141		}
142		for _, child := range fields {
143			if err := validateWireValue(child, kind.Elem()); err != nil {
144				return err
145			}
146		}
147	}
148	return nil
149}
150func fmtField(name string, err error) error { return errors.New(name + ": " + err.Error()) }
151
152// optionalText omits unset optional request fields.
153func optionalText(text string) *string {
154	if text == "" {
155		return nil
156	}
157	return &text
158}