1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#!/usr/bin/env python3
"""Prepare the OpenAPI spec for the openapi-generator Rust backend.
Two rewrites, both applied ONLY to the throwaway spec fed to the generator in
``regenerate.yml``; the canonical spec in www.hotdata.dev (and the Python/TS
SDKs) are untouched.
1. Flatten ``$ref``-with-siblings, which the Rust backend cannot name.
2. Name the anonymous result-row cell schema ``JsonCell``, so the generator can
be told a hand-written type supplies it.
Rewrite 1 — ``$ref``-with-siblings
OpenAPI 3.1 (JSON Schema 2020-12) lets a ``$ref`` carry sibling keywords — e.g. a
per-branch ``description`` on a ``oneOf`` member. That is valid, and the Python
and TypeScript generators consume it fine, but the Rust generator NPEs in
``AbstractRustCodegen.toModelName`` because it materializes the branch as an
*unnamed* inline schema. (Reproduced on generator 7.20.0 and 7.22.0; the upstream
backend has no fix yet.) The fatal case in our spec is ``JobResult``'s ``oneOf``,
whose four ``$ref`` branches each carry a ``description``.
We don't need those siblings in the generated Rust client — per-branch ``oneOf``
docs don't render in Rust regardless — so for every node that has a ``$ref``
alongside other keys we drop the other keys, leaving a pure ref the generator can
name.
Rewrite 2 — the row-cell schema
A result row is spelled ``rows: array of array of {}`` — an unnamed "any JSON
value" cell — which the Rust backend renders as ``serde_json::Value``. That
type has no arbitrary-precision number, so a decimal wider than an ``f64``
(``DECIMAL(38,2)`` at full width, say) is rounded the moment the response body
is parsed, before a caller can see it. Naming the cell schema lets
``regenerate.yml`` pass ``--import-mappings=JsonCell=crate::models::JsonCell``,
which makes the generator emit ``Vec<Vec<models::JsonCell>>`` for every
``rows`` field and skip writing the model — leaving the hand-written
``src/models/json_cell.rs``, which holds each cell's JSON text, to supply it.
Usage: ``normalize-openapi.py <spec.yaml>`` (edits the file in place).
"""
"""Recursively drop keys that sit alongside a ``$ref``. Returns the count."""
= 0
del
+= 1
+=
+=
return
#: Name given to the row-cell schema. Must match the ``--type-mappings`` /
#: ``--import-mappings`` pair in ``regenerate.yml`` and the hand-written
#: ``src/models/json_cell.rs`` that supplies the type.
=
"""Point every ``rows`` cell at a named ``JsonCell`` schema.
Matches on shape rather than on a list of model names: a ``rows`` property
that is an array of arrays whose element schema is empty. Returns the
models rewritten.
"""
return
=
=
continue
=
continue
=
# rows is an array of rows; a row is an array of cells; a cell is any
# JSON value, i.e. an empty schema.
continue
=
# Already pointed at the cell schema: a re-run over a spec this script
# has seen. Count it as done rather than as a miss, so a second run is
# a no-op and not a failure.
continue
# Anything else already typed is a shape this script does not know.
continue
=
=
return
return 2
=
=
=
# Fail the regen rather than emit a client that silently rounds decimals:
# if the spec ever stops spelling result rows this way, the substitution
# has to be re-aimed by hand, and a green regen PR would hide that.
=
return 1
return 0