Skip to content

Commit 22a2388

Browse files
committed
openapi
1 parent df90765 commit 22a2388

File tree

4 files changed

+45
-15
lines changed

4 files changed

+45
-15
lines changed

srv/codegen_apistuff.go

+23-3
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,28 @@ func codegenOpenApi(apiRefl *apiReflect) (didFsWrites []string) {
209209
out_file_path := curMainStaticDirPath_App + "/openapi.json"
210210

211211
openapi := yopenapi.OpenApi{
212-
Info: yopenapi.Info{Title: Cfg.YO_APP_DOMAIN, Version: time.Now().Format("06.__2")},
213212
OpenApi: yopenapi.Version,
214213
Paths: map[string]yopenapi.Path{},
214+
Info: yopenapi.Info{Title: Cfg.YO_APP_DOMAIN, Version: time.Now().Format("06.__2"), Descr: str.Replace(`
215+
Request/response rules and how to read their example renditions:
216+
- All request headers, URL query-string parameters and response headers are identical over the whole set of all operations. Only request and response bodies are operation-specific.
217+
- Request bodies **must never** be empty or the JSON ´null´: the empty request body is the JSON ´{}´.
218+
- Response bodies will never be empty, but may be the JSON ´null´.
219+
- All request fields are by default optional and ommittable / ´null´able, **any exceptions** to this are indicated by the operation's listed known-error responses.
220+
- Example values rendered in this doc:
221+
- ´true´ indicates any ´boolean´ value, regardless of the actual real value in a call
222+
- ´"someStr"´ indicates any ´string´ value
223+
- date-time values are indicated by RFC3339-formatted ´string´ examples
224+
- signed-integer ´number´s are indicated by a negative-number example indicating the minimum (type-wise, not operation-specific) permissible value, with the maximum being the corresponding positive-number counterpart
225+
- unsigned-integer ´number´s are indicated by a positive-number example indicating the maximum (type-wise, not not operation-specific) permissible value, with the minimum being ´0´
226+
- floating-point ´number´s are indicated by a positive-number example indicating the maximum (type-wise, not not operation-specific) permissible value, with the minimum being the corresponding negative-number counterpart
227+
228+
More about error responses:
229+
- All are ´text/plain´.
230+
- In addition to those listed in this doc (thrown by the service under the indicated conditions), other error responses are always entirely possible and not exhaustively documentable feasibly (such as DB, file-system or network disruptions). Those caught by the service will be ´500´s, others (ie. from load-balancers / gateways / reverse-proxies etc. in front of the service) might have any HTTP status code.
231+
- The well-known error responses listed here have been recursively determined by code-path walking. Among them are some that logically could not possibly ever occur for that operation, yet identifying those (to filter them out of the listing) is (so far) out of scope for our ´openapi.json´ generation.
232+
- The well-known (thrown rather than caught) errors have their code-identifier-compatible (spaceless ASCII) enumerant-name as their entire text response, others preserve simply their original (usually human-language) error message fully. Hence, error responses are inherently ´switch/case´able.
233+
`, str.Dict{"´": "`"})},
215234
}
216235
openapi.Info.Contact.Name, openapi.Info.Contact.Url = "Permalink", "https://"+Cfg.YO_APP_DOMAIN+"/"+StaticFilesDirName_App+"/"+filepath.Base(out_file_path)
217236

@@ -244,9 +263,10 @@ func codegenOpenApi(apiRefl *apiReflect) (didFsWrites []string) {
244263
},
245264
}}
246265
for http_status_code, errs := range sl.Grouped(api_method.KnownErrs(), func(it Err) string { return str.FromInt(it.HttpStatusCodeOr(500)) }) {
266+
str_errs := sl.As(errs, Err.String)
247267
path.Post.Responses[http_status_code] = yopenapi.Resp{
248-
Descr: "foo",
249-
Content: map[string]yopenapi.Media{"text/plain": {Examples: kv.FromKeys(errs, func(it Err) yopenapi.Example { return yopenapi.Example{Value: it} })}},
268+
Descr: "Possible `text/plain` responses:\n- `" + str.Join(str_errs, "`\n- `") + "`",
269+
Content: map[string]yopenapi.Media{"text/plain": {Examples: kv.FromKeys(str_errs, func(it string) yopenapi.Example { return yopenapi.Example{Value: it} })}},
250270
}
251271
}
252272
openapi.Paths["/"+method.Path] = path

srv/openapi/openapi.go

+17-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"reflect"
88
"time"
99

10+
. "yo/cfg"
1011
. "yo/util"
12+
"yo/util/str"
1113
)
1214

1315
// yoValiOnly yoFail
@@ -22,6 +24,8 @@ type OpenApi struct {
2224

2325
type Info struct {
2426
Title string `json:"title"`
27+
Summary string `json:"summary,omitempty"`
28+
Descr string `json:"description,omitempty"`
2529
Version string `json:"version"`
2630
Contact struct {
2731
Name string `json:"name"`
@@ -84,15 +88,16 @@ type Example struct {
8488

8589
var tyTime = reflect.TypeOf(time.Time{})
8690

87-
func dummyOf(ty reflect.Type, level int, typesDone map[reflect.Type]bool) reflect.Value {
91+
// TODO: type-recursion-safety
92+
func dummyOf(ty reflect.Type, level int) reflect.Value {
8893
dummy_ptr := func(dummy reflect.Value) reflect.Value {
8994
alloc := reflect.New(dummy.Type())
9095
alloc.Elem().Set(dummy)
9196
return alloc
9297
}
9398

9499
if ty.Kind() == reflect.Pointer {
95-
return dummyOf(ty.Elem(), level, typesDone)
100+
return dummyOf(ty.Elem(), level)
96101
}
97102
if ty.ConvertibleTo(tyTime) || tyTime.ConvertibleTo(ty) || ty.AssignableTo(tyTime) || tyTime.AssignableTo(ty) {
98103
return reflect.ValueOf(time.Now()).Convert(ty)
@@ -131,26 +136,31 @@ func dummyOf(ty reflect.Type, level int, typesDone map[reflect.Type]bool) reflec
131136
case reflect.Slice:
132137
dummy = reflect.MakeSlice(ty, 0, 1)
133138
ty_item := ty.Elem()
134-
append_item := dummyOf(ty_item, level+1, typesDone)
139+
append_item := dummyOf(ty_item, level+1)
135140
if ty_item.Kind() == reflect.Pointer {
136141
append_item = dummy_ptr(append_item)
137142
}
138143
dummy = reflect.Append(dummy, append_item)
139144
case reflect.Map:
140145
dummy = reflect.MakeMap(ty)
141146
ty_item := ty.Elem()
142-
append_item := dummyOf(ty_item, level+1, typesDone)
147+
append_item := dummyOf(ty_item, level+1)
143148
if ty_item.Kind() == reflect.Pointer {
144149
append_item = dummy_ptr(append_item)
145150
}
146-
dummy.SetMapIndex(reflect.ValueOf("someKey"), append_item)
151+
dummy.SetMapIndex(reflect.ValueOf("someMapKey1"), append_item)
152+
dummy.SetMapIndex(reflect.ValueOf("some_map_key_2"), append_item)
147153
case reflect.Struct:
148154
for i := 0; i < ty.NumField(); i++ {
149155
field := ty.Field(i)
150156
if !field.IsExported() {
151157
continue
152158
}
153-
field_dummy := dummyOf(field.Type, level+1, typesDone)
159+
if str.Has(str.Lo(field.Name), "password") {
160+
dummy.Field(i).Set(reflect.ValueOf("lenOfMin" + str.FromInt(Cfg.YO_AUTH_PWD_MIN_LEN) + "AndMax" + str.FromInt(Cfg.YO_AUTH_PWD_MAX_LEN)))
161+
continue
162+
}
163+
field_dummy := dummyOf(field.Type, level+1)
154164
if field.Type.Kind() == reflect.Pointer && field_dummy.Kind() != reflect.Pointer {
155165
field_dummy = dummy_ptr(field_dummy)
156166
}
@@ -159,10 +169,9 @@ func dummyOf(ty reflect.Type, level int, typesDone map[reflect.Type]bool) reflec
159169
}
160170
}
161171
}
162-
163172
return dummy
164173
}
165174

166175
func DummyOf(ty reflect.Type) any {
167-
return dummyOf(ty, 0, map[reflect.Type]bool{}).Interface()
176+
return dummyOf(ty, 0).Interface()
168177
}

util/err.go

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type Err string
66

77
var errSubstrToHttpStatusCode = map[string]int{
88
"DoesNotExist": 400, // no 404 wanted for those, there's NotFound below for that
9+
"ContentLength": 400,
910
"WrongPassword": 401,
1011
"MustBeAdmin": 401,
1112
"Unauthorized": 403,

util/kv/kv.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@ func Fill[K comparable, V any](dst map[K]V, from map[K]V) map[K]V {
1818
return dst
1919
}
2020

21-
func FromKeys[K comparable, V any](keys []K, value func(K) V) map[K]V {
22-
ret := make(map[K]V, len(keys))
21+
func FromKeys[TKey comparable, TVal any](keys []TKey, value func(TKey) TVal) map[TKey]TVal {
22+
ret := make(map[TKey]TVal, len(keys))
2323
for _, key := range keys {
2424
ret[key] = value(key)
2525
}
2626
return ret
2727
}
2828

29-
func FromValues[K comparable, V any](values []V, key func(V) K) map[K]V {
30-
ret := make(map[K]V, len(values))
29+
func FromValues[TKey comparable, TVal any](values []TVal, key func(TVal) TKey) map[TKey]TVal {
30+
ret := make(map[TKey]TVal, len(values))
3131
for _, val := range values {
3232
ret[key(val)] = val
3333
}

0 commit comments

Comments
 (0)