Skip to content

Commit 0679ce9

Browse files
committed
openapi
1 parent 10630a4 commit 0679ce9

File tree

4 files changed

+36
-32
lines changed

4 files changed

+36
-32
lines changed

ctx/ctx.go

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const (
2323
CtxKeyForcedTestUser = "yoUserTest" // handled only with IsDevMode==true
2424
CtxKeyDbNoLogging = "yoCtxDbNoLogging"
2525
HttpResponseHeaderName_UserId = "X-Yo-User"
26+
MimeTypePlainText = "text/plain"
2627
)
2728

2829
var (

mail/mail.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func composeMimeMail(subject string, body string) []byte {
8686
for k, v := range (str.Dict{
8787
"Subject": subject,
8888
"MIME-Version": "1.0",
89-
"Content-Type": "text/plain; charset=\"utf-8\"",
89+
"Content-Type": MimeTypePlainText + "; charset=\"utf-8\"",
9090
"Content-Transfer-Encoding": "base64",
9191
}) {
9292
raw_msg += k + ": " + v + "\r\n"

srv/codegen_apistuff.go

+33-29
Original file line numberDiff line numberDiff line change
@@ -216,29 +216,28 @@ func codegenOpenApi(apiRefl *apiReflect) (didFsWrites []string) {
216216
Descr: str.Repl(str.Replace(`
217217
This HTTP API has RPC rather than REST semantics: **all** operations are ´POST´, regardless of what CRUD writes or reads they might or might not effect.
218218
219-
**tl;dr:** mostly **API calls will just-work as expected _without_ knowing all those intro notes** immediately below (which elaborate mostly to-be-expected software-dev-commonplaces),
220-
- but in any cases of unexpected results or errors, they'll likely help complete the mental picture.
219+
**tl;dr:** **usually, API requests will just-work as expected _without_ knowing all those intro notes right below** (which elaborate mostly to-be-expected software-dev-commonplaces) — but in any cases of unexpected results or errors, they'll likely help complete the mental picture.
221220
___
222-
Our backend stack's convention-over-configuration designs yield a few request/response rules that remain **always in effect across all listed operations**:
221+
Our backend stack's "opinionated convention-over-configuration" designs yield a few request/response rules that predictably remain **always in effect across all listed operations**:
223222
- Whereas request and response bodies are operation-specific, all operations share the exact-same set of request headers, URL query-string parameters and response headers (albeit being elaborated here identically and redundantly for each individual operation).
224-
- Request bodies **must never** be empty or the JSON ´null´: the empty request body is the JSON ´{}´.
223+
- The empty request body is principally the JSON ´{}´, but fully-empty or JSON ´null´ request bodies are interpreted equivalently.
225224
- Response bodies will never be empty, but may be the JSON ´null´.
226225
- Request and response bodies are always valid JSON values for _JSON objects_, ie. they're never immediately JSON arrays, ´string´s, ´number´s, or ´boolean´s.
227-
- All mentioned request-object (and sub-object) fields are **by default optional** and ommittable / ´null´able,
228-
- **any exceptions** to this are indicated by the operation's listed known-error responses.
226+
- All mentioned request-object (and sub-object) fields are **by default optional** and ommittable or ´null´able (implying for atomic types semantic equivalence to ´""´ or ´0´ or ´false´ as per _Golang_ type-system semantics),
227+
- **any exceptions** to this optionality-by-default are indicated by the operation's listed known-error responses.
229228
- All mentioned response-object (and sub-object) fields will always be present in the response-body, indicating their default-ness / missing-ness via ´null´ or ´""´ or ´0´ or ´false´ as per _Golang_ type-system semantics.
230-
- Caution for some client languages: this means ´null´ for many-if-not-most empty JSON arrays (although ´[]´ is principally always just-as-possible) and empty JSON dictionary/hash-map "object"s (with either ´null´ or ´{}´ principally equally possible).
231-
- All JSON object field names begin with an upper-case character,
232-
- any example to the contrary indicates a "free-style" JSON dictionary/hash-map "object".
229+
- Caution for some client languages: this means ´null´ for some-but-not-all empty JSON arrays (with ´[]´ being principally always just-as-possible) and empty JSON dictionary/hash-map "object"s (with either ´null´ or ´{}´ being principally equally possible).
230+
- All JSON (non-dictionary/non-hash-map) object field names begin with an upper-case character,
231+
- any operation-specific example rendered to the contrary indicates a "free-style" JSON dictionary/hash-map "object".
233232
- The ´Content-Length´ request header is **required for all** operations (with a correct value).
234233
- The ´Content-Type´ request header is optional, but if present, must be correct with regards to both the operation's specification and the request body.
235-
- Any ´multipart/form-data´ operations:
236-
- **always require** the following two form-fields: ´files´ for any binary file uploads, and ´_´ for the actual JSON request payload;
237-
- only the latter is elaborated in this doc, and always in the exact same way as done for all the ´application/json´ operations, **without** specifically mentioning the ´_´ form-field containing the ´text/plain´ of the full ´application/json´ request payload actually being elaborated here.
234+
- Any ´{ctype_multipart}´ operations:
235+
- **always require** the following two form-fields, ignoring all others: ´files´ for any binary file uploads, and ´_´ for the actual JSON request payload;
236+
- only the ´_´ field value is further elaborated for any such operation in this doc, and always in the exact same way as also done in this doc for all the ´{ctype_json}´ operations' request bodies (**without** specifically mentioning the ´_´ form-field containing the ´{ctype_text}´ of the full ´{ctype_json}´ request payload actually being elaborated there).
238237
239238
How to read request/response **example JSON values** rendered in this doc:
240-
- ´true´ indicates any ´boolean´ value, regardless of the actual real value in a call;
241-
- ´"someStr"´ indicates any ´string´ value;
239+
- ´true´ indicates _any_ ´boolean´ value, regardless of the actual real value in a call;
240+
- ´"someStr"´ indicates _any_ ´string´ value;
242241
- 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;
243242
- 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´;
244243
- 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.
@@ -247,17 +246,22 @@ How to read request/response **example JSON values** rendered in this doc:
247246
- in requests, they may always be ´null´ (excepting any operation-specific known-errors indicating otherwise) but must never be ´""´ or otherwise non-RFC3339/ISO8601-parseable.
248247
249248
About **error responses**:
250-
- All are ´text/plain´.
251-
- In addition to those listed in this doc (thrown by the service under the indicated conditions), other error responses are at all times entirely technically-possible and not exhaustively documentable (feasibly), such as eg. DB / file-system / 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 whatsoever.
249+
- All are ´{ctype_text}´.
250+
- In addition to those listed in this doc (thrown by the service under the indicated conditions), other error responses are at all times entirely technically-possible and not exhaustively documentable (feasibly), such as eg. DB / file-system / 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 use _any_ HTTP status code whatsoever.
252251
- All the well-known (thrown rather than caught) errors listed here:
253252
- have their code-identifier-compatible (spaceless ASCII) enumerant-name as their entire text response, making all error responses inherently ´switch/case´able;
254-
- 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.
253+
- 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 current ´{spec_file_name}´ spec generator. (In case of serious need, do let us know!)
255254
- Any non-known (caught rather than thrown) errors (not listed here) contain their original (usually human-language) error message fully, corresponding to the ´default´ in an error-handling ´switch/case´.
256-
- "Not Found" rules:
257-
- ´404´ **only** for HTTP requests of non-existing API operations or non-existing static-file assets,
258-
- ´400´ for operations where existence was definitely expected (such as some object's update identified by its ´Id´),
259-
- ´200´ with response-body of JSON ´null´ for requests of the "fetch single/first object found for some specified criteria" kind (where the definite-existence expectation does not necessarily hold).
260-
`, str.Dict{"´": "`"}), str.Dict{})},
255+
- **"Not Found" rules:**
256+
- ´406´ with approx. ´FooDoesNotExist´ (see per-operation known-error-responses listings for exact monikers) — for operations where existence was definitely critically expected (such as modifying some object identified by its ´Id´),
257+
- ´200´ with response-body of usually JSON ´null´ (exception: an operation-specific response object with a field name obviously indicating not-found-ness) — for operations where the definite-existence expectation does not hold as crucially (for example those of the "fetch single/first object found for some specified criteria" kind),
258+
- ´404´ for HTTP requests with definitely-unroutable URL paths (ie. "no such API operation or static-file asset or sub-site or etc.") **only**.
259+
`, str.Dict{"´": "`"}), str.Dict{
260+
"spec_file_name": filepath.Base(out_file_path),
261+
"ctype_multipart": apisContentType_Multipart,
262+
"ctype_json": apisContentType_Json,
263+
"ctype_text": yoctx.MimeTypePlainText,
264+
})},
261265
}
262266
openapi.Info.Contact.Name, openapi.Info.Contact.Url = "Permalink of "+filepath.Base(out_file_path), "https://"+Cfg.YO_APP_DOMAIN+"/"+StaticFilesDirName_App+"/"+filepath.Base(out_file_path)
263267

@@ -271,8 +275,8 @@ About **error responses**:
271275
path := yopenapi.Path{Post: yopenapi.Op{
272276
Id: api_method.methodNameUp0(),
273277
Params: []yopenapi.Param{
274-
{Name: QueryArgForceFail, In: "query", Descr: "optional: if not missing or empty, enforces an early error response (prior to any request parsing or handling) with the specified HTTP status code or 500 (eg. for client-side unit-test cases of error-handling)", Content: map[string]yopenapi.Media{"text/plain": {Example: ""}}},
275-
{Name: QueryArgValidateOnly, In: "query", Descr: "optional: if not missing or empty, enforces request-validation-only, with no further actual work performed to produce results and/or effects", Content: map[string]yopenapi.Media{"text/plain": {Example: ""}}},
278+
{Name: QueryArgForceFail, In: "query", Descr: "optional: if not missing or empty, enforces an early error response (prior to any request parsing or handling) with the specified HTTP status code or 500 (eg. for client-side unit-test cases of error-handling)", Content: map[string]yopenapi.Media{yoctx.MimeTypePlainText: {Example: ""}}},
279+
{Name: QueryArgValidateOnly, In: "query", Descr: "optional: if not missing or empty, enforces request-validation-only, with no further actual work performed to produce results and/or effects", Content: map[string]yopenapi.Media{yoctx.MimeTypePlainText: {Example: ""}}},
276280
},
277281
ReqBody: yopenapi.ReqBody{
278282
Required: true,
@@ -284,25 +288,25 @@ About **error responses**:
284288
Descr: "Type ident: `" + method.Out + "`",
285289
Content: map[string]yopenapi.Media{apisContentType_Json: {Example: dummy_ret}},
286290
Headers: map[string]yopenapi.Header{
287-
yoctx.HttpResponseHeaderName_UserId: {Descr: "`0` if not authenticated, else current `User`'s `Id`", Content: map[string]yopenapi.Media{"text/plain": {Example: "123"}}},
291+
yoctx.HttpResponseHeaderName_UserId: {Descr: "`0` if not authenticated, else current `User`'s `Id`", Content: map[string]yopenapi.Media{yoctx.MimeTypePlainText: {Example: "123"}}},
288292
},
289293
},
290294
},
291295
}}
292296
for http_status_code, errs := range sl.Grouped(api_method.KnownErrs(), func(it Err) string { return str.FromInt(it.HttpStatusCodeOr(500)) }) {
293297
str_errs := sl.As(errs, Err.String)
294298
path.Post.Responses[http_status_code] = yopenapi.Resp{
295-
Descr: "Possible `text/plain` responses:\n- `" + str.Join(str_errs, "`\n- `") + "`",
296-
Content: map[string]yopenapi.Media{"text/plain": {Examples: kv.FromKeys(str_errs, func(it string) yopenapi.Example { return yopenapi.Example{Value: it} })}},
299+
Descr: "Possible `" + yoctx.MimeTypePlainText + "` responses:\n- `" + str.Join(str_errs, "`\n- `") + "`",
300+
Content: map[string]yopenapi.Media{yoctx.MimeTypePlainText: {Examples: kv.FromKeys(str_errs, func(it string) yopenapi.Example { return yopenapi.Example{Value: it} })}},
297301
Headers: map[string]yopenapi.Header{},
298302
}
299303
}
300304
for http_status_code := range path.Post.Responses {
301305
resp := path.Post.Responses[http_status_code]
302306
for header_name, header_value := range apisStdRespHeaders {
303307
resp.Headers[header_name] = yopenapi.Header{Descr: "always `" +
304-
If((header_name == "Content-Type") && (http_status_code != "200"), "text/plain", header_value) +
305-
"`", Content: map[string]yopenapi.Media{"text/plain": {Example: header_value}}}
308+
If((header_name == "Content-Type") && (http_status_code != "200"), yoctx.MimeTypePlainText, header_value) +
309+
"`", Content: map[string]yopenapi.Media{yoctx.MimeTypePlainText: {Example: header_value}}}
306310
}
307311
path.Post.Responses[http_status_code] = resp
308312
}

util/err.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ import "yo/util/str"
55
type Err string
66

77
var errSubstrToHttpStatusCode = map[string]int{
8-
"DoesNotExist": 400, // no 404 wanted for those, there's NotFound below for that
98
"WrongPassword": 401,
109
"MustBeAdmin": 401,
1110
"Unauthorized": 403,
1211
"Forbidden": 403,
13-
"NotFound": 404,
12+
"DoesNotExist": 406, // no 404 wanted for those, that's for no-such-api-method-or-static-file-or-subsite only
1413
"Unacceptable": 406,
1514
"AlreadyExists": 409,
1615
"Required": 422,

0 commit comments

Comments
 (0)