@nxgt/typespec 0.9.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -14
- package/docs/README.md +2 -0
- package/docs/guide/auth.md +1 -1
- package/docs/guide/columns.md +29 -15
- package/docs/guide/errors.md +1 -1
- package/docs/guide/getting-started.md +16 -7
- package/docs/guide/headers.md +2 -2
- package/docs/guide/operation-ids.md +1 -1
- package/docs/guide/requests.md +145 -0
- package/docs/guide/scalars.md +288 -0
- package/docs/roadmap.md +22 -0
- package/docs/troubleshooting.md +95 -3
- package/lib/main.tsp +1 -0
- package/lib/requests.tsp +36 -0
- package/lib/scalars/color/hex-color-code.tsp +17 -0
- package/lib/scalars/color/hsl.tsp +18 -0
- package/lib/scalars/color/hsla.tsp +18 -0
- package/lib/scalars/color/rgb.tsp +18 -0
- package/lib/scalars/color/rgba.tsp +18 -0
- package/lib/scalars/color.tsp +6 -0
- package/lib/scalars/date-time/date-time.tsp +19 -0
- package/lib/scalars/date-time/date.tsp +17 -0
- package/lib/scalars/date-time/iso-duration.tsp +17 -0
- package/lib/scalars/date-time/local-date-time.tsp +15 -0
- package/lib/scalars/date-time/local-time.tsp +15 -0
- package/lib/scalars/date-time/time-zone.tsp +18 -0
- package/lib/scalars/date-time/time.tsp +17 -0
- package/lib/scalars/date-time/timestamp.tsp +15 -0
- package/lib/scalars/date-time/utc-offset.tsp +15 -0
- package/lib/scalars/date-time.tsp +10 -0
- package/lib/scalars/encoding/base64-url.tsp +17 -0
- package/lib/scalars/encoding/base64.tsp +18 -0
- package/lib/scalars/encoding/hexadecimal.tsp +15 -0
- package/lib/scalars/encoding/jwt.tsp +19 -0
- package/lib/scalars/encoding/sha256.tsp +16 -0
- package/lib/scalars/encoding/sha512.tsp +16 -0
- package/lib/scalars/encoding.tsp +7 -0
- package/lib/scalars/finance/currency.tsp +17 -0
- package/lib/scalars/finance/iban.tsp +19 -0
- package/lib/scalars/finance.tsp +3 -0
- package/lib/scalars/geo/latitude.tsp +14 -0
- package/lib/scalars/geo/longitude.tsp +14 -0
- package/lib/scalars/geo.tsp +3 -0
- package/lib/scalars/identifier/cuid2.tsp +17 -0
- package/lib/scalars/identifier/guid.tsp +12 -0
- package/lib/scalars/identifier/isbn.tsp +18 -0
- package/lib/scalars/identifier/ksuid.tsp +18 -0
- package/lib/scalars/identifier/nano-id.tsp +16 -0
- package/lib/scalars/identifier/object-id.tsp +16 -0
- package/lib/scalars/identifier/sem-ver.tsp +17 -0
- package/lib/scalars/identifier/ulid.tsp +17 -0
- package/lib/scalars/identifier/uuid-v4.tsp +16 -0
- package/lib/scalars/identifier/uuid-v7.tsp +16 -0
- package/lib/scalars/identifier/uuid.tsp +18 -0
- package/lib/scalars/identifier/xid.tsp +17 -0
- package/lib/scalars/identifier.tsp +13 -0
- package/lib/scalars/locale/country-code.tsp +17 -0
- package/lib/scalars/locale/locale.tsp +20 -0
- package/lib/scalars/locale.tsp +3 -0
- package/lib/scalars/network/cidr-v4.tsp +19 -0
- package/lib/scalars/network/cidr-v6.tsp +18 -0
- package/lib/scalars/network/email-address.tsp +20 -0
- package/lib/scalars/network/hostname.tsp +21 -0
- package/lib/scalars/network/http-url.tsp +21 -0
- package/lib/scalars/network/ip-v4.tsp +17 -0
- package/lib/scalars/network/ip-v6.tsp +18 -0
- package/lib/scalars/network/ip.tsp +17 -0
- package/lib/scalars/network/mac.tsp +16 -0
- package/lib/scalars/network/phone-number.tsp +18 -0
- package/lib/scalars/network.tsp +11 -0
- package/lib/scalars/number/big-int.tsp +15 -0
- package/lib/scalars/number/long.tsp +17 -0
- package/lib/scalars/number/negative-float.tsp +13 -0
- package/lib/scalars/number/negative-int.tsp +15 -0
- package/lib/scalars/number/non-negative-float.tsp +13 -0
- package/lib/scalars/number/non-negative-int.tsp +15 -0
- package/lib/scalars/number/non-positive-float.tsp +13 -0
- package/lib/scalars/number/non-positive-int.tsp +15 -0
- package/lib/scalars/number/port.tsp +14 -0
- package/lib/scalars/number/positive-float.tsp +13 -0
- package/lib/scalars/number/positive-int.tsp +15 -0
- package/lib/scalars/number/safe-int.tsp +16 -0
- package/lib/scalars/number.tsp +13 -0
- package/lib/scalars/string/emoji.tsp +21 -0
- package/lib/scalars/string/non-empty-string.tsp +15 -0
- package/lib/scalars/string.tsp +3 -0
- package/lib/scalars/value.tsp +2 -0
- package/lib/scalars.tsp +13 -12
- package/package.json +3 -3
- package/templates/nxgt/main.tsp +11 -4
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Scalars
|
|
2
|
+
|
|
3
|
+
The scalars of [`@nxgt/graphql-scalars`](https://www.npmjs.com/package/@nxgt/graphql-scalars)
|
|
4
|
+
as TypeSpec scalars, so a spec types a latitude, a currency or an IPv4 address
|
|
5
|
+
once and every generator and validator downstream agrees on it.
|
|
6
|
+
|
|
7
|
+
```tsp
|
|
8
|
+
import "@typespec/http";
|
|
9
|
+
import "@nxgt/typespec";
|
|
10
|
+
|
|
11
|
+
using Http;
|
|
12
|
+
using Nxgt;
|
|
13
|
+
|
|
14
|
+
@service(#{ title: "Places" })
|
|
15
|
+
namespace Places;
|
|
16
|
+
|
|
17
|
+
model Place {
|
|
18
|
+
name: string;
|
|
19
|
+
location: {
|
|
20
|
+
latitude: latitude;
|
|
21
|
+
longitude: longitude;
|
|
22
|
+
};
|
|
23
|
+
currency: currency;
|
|
24
|
+
openedOn: date;
|
|
25
|
+
homepage?: httpUrl;
|
|
26
|
+
contact?: emailAddress;
|
|
27
|
+
seats: positiveInt;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@route("/places") @post op createPlace(@body place: Place): Place;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Each property becomes a `$ref` to a component named as the GraphQL scalar:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
components:
|
|
37
|
+
schemas:
|
|
38
|
+
Currency:
|
|
39
|
+
type: string
|
|
40
|
+
pattern: ^[A-Z]{3}$
|
|
41
|
+
format: currency
|
|
42
|
+
description: |-
|
|
43
|
+
An ISO 4217 currency code, uppercase: `EUR`, `USD`. ...
|
|
44
|
+
x-nxgt-scalar: Currency
|
|
45
|
+
Latitude:
|
|
46
|
+
type: number
|
|
47
|
+
format: double
|
|
48
|
+
minimum: -90
|
|
49
|
+
maximum: 90
|
|
50
|
+
x-nxgt-scalar: Latitude
|
|
51
|
+
PositiveInt:
|
|
52
|
+
type: integer
|
|
53
|
+
format: int32
|
|
54
|
+
minimum: 1
|
|
55
|
+
maximum: 2147483647
|
|
56
|
+
x-nxgt-scalar: PositiveInt
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## What a scalar emits
|
|
60
|
+
|
|
61
|
+
| Emitted | Value |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| The component | The GraphQL name (`@friendlyName`): `Latitude`, `CIDRv4`, `EmailAddress`. It is global: a model of that name in your spec collides with it. |
|
|
64
|
+
| `format` | JSON Schema's when one exists (`date`, `date-time`, `duration`, `email`, `hostname`, `ipv4`, `ipv6`, `uri`, `uuid`), otherwise the kebab-case name (`iban`, `local-time`, `cidr-v4`); `base64` has OpenAPI's `byte`. The numbers keep the format TypeSpec writes from the base type. |
|
|
65
|
+
| `pattern`, `minimum`, `maximum` | The closest the GraphQL rule goes in a pattern or in bounds. |
|
|
66
|
+
| `x-nxgt-scalar` | The GraphQL name, so a generator can tell the rule from the shape. |
|
|
67
|
+
| `description` | The rule in words, and what the pattern leaves out. |
|
|
68
|
+
|
|
69
|
+
A generator that knows nothing of `x-nxgt-scalar` still validates the pattern
|
|
70
|
+
and the bounds. A later `@nxgt/openapi-codegen` release will map it to the exact
|
|
71
|
+
Zod schema from `@nxgt/zod` ([roadmap](../roadmap.md)); until then, what a
|
|
72
|
+
scalar leaves out is not checked.
|
|
73
|
+
|
|
74
|
+
## Names
|
|
75
|
+
|
|
76
|
+
The identifiers are camelCase: `ipV4`, `cidrV6`, `uuidV7`, `hexColorCode`. The
|
|
77
|
+
component keeps the GraphQL spelling.
|
|
78
|
+
|
|
79
|
+
- `url` and `duration` are TypeSpec built-ins, and a scalar of that name makes
|
|
80
|
+
`url` ambiguous under `using Nxgt`. They are `httpUrl` and `isoDuration`.
|
|
81
|
+
- `JSON`, `JSONObject` and `Void` are not declared: write TypeSpec's own
|
|
82
|
+
`unknown`, `Record<unknown>` and `void`.
|
|
83
|
+
- A declaration of yours named like a scalar shadows it. In a spec with an
|
|
84
|
+
operation `locale`, write `Nxgt.locale`.
|
|
85
|
+
|
|
86
|
+
```tsp
|
|
87
|
+
@get op locale(): string; // yours
|
|
88
|
+
model Page { language: Nxgt.locale; } // the scalar
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Identifiers
|
|
92
|
+
|
|
93
|
+
`uuid`, with the id columns, is also in [Scalars and columns](columns.md).
|
|
94
|
+
|
|
95
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
96
|
+
| --- | --- | --- | --- | --- |
|
|
97
|
+
| `uuid` | `UUID` | `uuid` | `550e8400-e29b-41d4-a716-446655440000`, the nil and the max UUID | |
|
|
98
|
+
| `uuidV4` | `UUIDv4` | `uuid` | `123e4567-e89b-42d3-a456-426614174000` | |
|
|
99
|
+
| `uuidV7` | `UUIDv7` | `uuid` | `017f22e2-79b0-7cc3-98c4-dc0c0c07398f` | |
|
|
100
|
+
| `guid` | `GUID` | `uuid` | any 8-4-4-4-12 hexadecimal, `123e4567-e89b-12d3-c456-426614174000` | no version, no variant |
|
|
101
|
+
| `ulid` | `ULID` | `ulid` | `01ARZ3NDEKTSV4RRFFQ69G5FAV`: 26 Crockford base32 characters, the first 0 to 7, any case | |
|
|
102
|
+
| `cuid2` | `Cuid2` | `cuid2` | `tz4a98xxat96iws9zmbrgj3a`, `ab`: a lowercase letter, then lowercase letters and digits, 2 to 32 characters | |
|
|
103
|
+
| `nanoId` | `NanoID` | `nano-id` | `V1StGXR8_Z5jdHi6B-myT`: 21 characters of `A-Za-z0-9_-`; a custom length is refused | |
|
|
104
|
+
| `ksuid` | `KSUID` | `ksuid` | `0ujtsYcgvSTl8PAuAdqWYSMnLOv`: 27 base62 characters, up to `aWgEPTl1tmebfsQzFP4bxwgy80V` | |
|
|
105
|
+
| `xid` | `XID` | `xid` | `9m4e2mr0ui3e8a215n4g`: 20 lowercase base32hex characters, the last `0` or `g` | |
|
|
106
|
+
| `objectId` | `ObjectID` | `object-id` | `507f1f77bcf86cd799439011`: 24 hexadecimal digits, any case | |
|
|
107
|
+
| `isbn` | `ISBN` | `isbn` | `0306406152`, `080442957X`, `9780306406157`: digits only, no hyphen | the check digit (`9780306406158` passes) |
|
|
108
|
+
| `semVer` | `SemVer` | `sem-ver` | `1.2.3`, `1.0.0-rc.1+build.5`: no `v` prefix, no leading zero | |
|
|
109
|
+
|
|
110
|
+
`uuid` refuses a UUID of no known version (`...-91d4-...`); `guid` is the shape
|
|
111
|
+
only. Case is kept as sent.
|
|
112
|
+
|
|
113
|
+
## Encodings
|
|
114
|
+
|
|
115
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
116
|
+
| --- | --- | --- | --- | --- |
|
|
117
|
+
| `base64` | `Base64` | `byte` | `aGk=`, `a+b/`, `""`: RFC 4648 with padding, in its one canonical spelling (`YR==` is refused) | |
|
|
118
|
+
| `base64Url` | `Base64URL` | `base64-url` | `aGk`, `a-b_`: `-` and `_`, no padding, canonical | |
|
|
119
|
+
| `hexadecimal` | `Hexadecimal` | `hexadecimal` | `deadBEEF`, `abc`: one or more digits, any case, no `0x` | |
|
|
120
|
+
| `jwt` | `JWT` | `jwt` | three base64url parts joined by `.`, `eyJhbGci….eyJzdWIi….dozjgNry…` | that each part is canonical and the first two are JSON objects, `alg: none`, the signature |
|
|
121
|
+
| `sha256` | `SHA256` | `sha256` | 64 hexadecimal digits, any case | |
|
|
122
|
+
| `sha512` | `SHA512` | `sha512` | 128 hexadecimal digits, any case | |
|
|
123
|
+
|
|
124
|
+
`base64` carries the OpenAPI format `byte`, which the generator turns into
|
|
125
|
+
`z.base64()` in front of the pattern (`zBase64 = z.base64().regex(…)`); the
|
|
126
|
+
others are `z.string().regex(…)`. A `jwt` is a shape, not a verification: check
|
|
127
|
+
the token's signature in your code.
|
|
128
|
+
|
|
129
|
+
## Strings
|
|
130
|
+
|
|
131
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
132
|
+
| --- | --- | --- | --- | --- |
|
|
133
|
+
| `nonEmptyString` | `NonEmptyString` | `non-empty-string` | `a`, ` a `: at least one non-white-space character (ECMAScript `\s`); kept as sent, not trimmed; `""`, `" "` and a lone no-break space are refused | |
|
|
134
|
+
| `emoji` | `Emoji` | `emoji` | `😀`, `👍🏽`, `👨👩👧`, `🇫🇷`, `1️⃣`: exactly one emoji | tag sequences (the flags of England, Scotland, Wales), segmentation into user-perceived characters, so two emoji in a row may pass |
|
|
135
|
+
|
|
136
|
+
The `emoji` pattern uses Unicode property escapes and needs the `u` flag;
|
|
137
|
+
the generated Zod regex has it.
|
|
138
|
+
|
|
139
|
+
## Numbers
|
|
140
|
+
|
|
141
|
+
Numbers are JSON numbers, except `BigInt` and `Long`, which are decimal strings
|
|
142
|
+
because a JSON number loses precision past 2^53.
|
|
143
|
+
|
|
144
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
145
|
+
| --- | --- | --- | --- | --- |
|
|
146
|
+
| `positiveInt` | `PositiveInt` | `int32` | `1` to `2147483647` | `-0` |
|
|
147
|
+
| `nonNegativeInt` | `NonNegativeInt` | `int32` | `0` to `2147483647` | `-0` |
|
|
148
|
+
| `negativeInt` | `NegativeInt` | `int32` | `-2147483648` to `-1` | `-0` |
|
|
149
|
+
| `nonPositiveInt` | `NonPositiveInt` | `int32` | `-2147483648` to `0` | `-0` |
|
|
150
|
+
| `safeInt` | `SafeInt` | `int64` | the integers JavaScript holds exactly, `-9007199254740991` to `9007199254740991` | `-0` |
|
|
151
|
+
| `port` | `Port` | `int32` | `0` to `65535` | `-0` |
|
|
152
|
+
| `positiveFloat` | `PositiveFloat` | `double` | `0.5`, above 0 | |
|
|
153
|
+
| `nonNegativeFloat` | `NonNegativeFloat` | `double` | `0`, `1.5` | |
|
|
154
|
+
| `negativeFloat` | `NegativeFloat` | `double` | `-0.5`, below 0 | |
|
|
155
|
+
| `nonPositiveFloat` | `NonPositiveFloat` | `double` | `0`, `-1.5` | |
|
|
156
|
+
| `bigInt` | `BigInt` | `big-int` | `"123456789012345678901234567890"`: no leading zero, no `+`, no `-0` | |
|
|
157
|
+
| `long` | `Long` | `int64` | `"9223372036854775807"`, the signed 64-bit range | |
|
|
158
|
+
|
|
159
|
+
`long` is a string with `format: int64`, the OpenAPI idiom for an int64 sent as
|
|
160
|
+
a string; `timestamp` and `safeInt` are JSON numbers with the same format.
|
|
161
|
+
|
|
162
|
+
An integer scalar accepts `-0`, which GraphQL refuses and no JSON Schema can
|
|
163
|
+
express.
|
|
164
|
+
|
|
165
|
+
## Date and time
|
|
166
|
+
|
|
167
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
168
|
+
| --- | --- | --- | --- | --- |
|
|
169
|
+
| `date` | `Date` | `date` | `2024-02-29` (an impossible day is refused) | |
|
|
170
|
+
| `dateTime` | `DateTime` | `date-time` | `2024-03-10T12:00:00+02:00`: an offset is required, `-00:00` refused | the range 0000 to 9999 in UTC, the leap second |
|
|
171
|
+
| `time` | `Time` | `time` | `10:15:30Z`, `10:15:30.5+02:00` | |
|
|
172
|
+
| `localDateTime` | `LocalDateTime` | `local-date-time` | `2024-03-10T10:15`, `2024-03-10T10:15:30`: no offset | |
|
|
173
|
+
| `localTime` | `LocalTime` | `local-time` | `10:15`, `10:15:30.5`: no offset | |
|
|
174
|
+
| `isoDuration` | `Duration` | `duration` | `P1Y2M3DT4H5M6S`, `PT0.5S`, `P2W`: weeks alone, no sign | |
|
|
175
|
+
| `utcOffset` | `UtcOffset` | `utc-offset` | `+05:30`, from `-12:00` to `+14:00`; `-00:00` refused | |
|
|
176
|
+
| `timeZone` | `TimeZone` | `time-zone` | `Europe/Paris`, `UTC` | the list of IANA names |
|
|
177
|
+
| `timestamp` | `Timestamp` | `int64` | milliseconds since the epoch, `1710065730000`, within ±8640000000000000 | |
|
|
178
|
+
|
|
179
|
+
A well-shaped unknown time zone name (`Mars/Olympus`) passes
|
|
180
|
+
([Known gaps](#known-gaps)).
|
|
181
|
+
|
|
182
|
+
## Geo
|
|
183
|
+
|
|
184
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
185
|
+
| --- | --- | --- | --- | --- |
|
|
186
|
+
| `latitude` | `Latitude` | `double` | `48.8566`, from -90 to 90 | |
|
|
187
|
+
| `longitude` | `Longitude` | `double` | `2.3522`, from -180 to 180 | |
|
|
188
|
+
|
|
189
|
+
Both are numbers, never strings.
|
|
190
|
+
|
|
191
|
+
## Colors
|
|
192
|
+
|
|
193
|
+
CSS color syntax, in the comma form, one space after each comma.
|
|
194
|
+
|
|
195
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
196
|
+
| --- | --- | --- | --- | --- |
|
|
197
|
+
| `hexColorCode` | `HexColorCode` | `hex-color-code` | `#f00`, `#f008`, `#ff0000`, `#ff000080`, any case | |
|
|
198
|
+
| `rgb` | `RGB` | `rgb` | `rgb(255, 0, 0)`; no percentage, no leading zero | |
|
|
199
|
+
| `rgba` | `RGBA` | `rgba` | `rgba(255, 0, 0, 0.5)`: the alpha is `0`, `1` or a fraction with a leading `0` and no trailing zero | |
|
|
200
|
+
| `hsl` | `HSL` | `hsl` | `hsl(120, 100%, 50%)`: hue 0 to 359, no unit | |
|
|
201
|
+
| `hsla` | `HSLA` | `hsla` | `hsla(120, 100%, 50%, 0.5)` | |
|
|
202
|
+
|
|
203
|
+
`rgb(255,0,0)`, `rgb(255 0 0)` and `RGB(255, 0, 0)` are refused.
|
|
204
|
+
|
|
205
|
+
## Network
|
|
206
|
+
|
|
207
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
208
|
+
| --- | --- | --- | --- | --- |
|
|
209
|
+
| `emailAddress` (alias `email`) | `EmailAddress` | `email` | `ada@example.com`, `a.b+c@sub.example.org`: no quoted local part, at least two labels | whether the domain exists |
|
|
210
|
+
| `hostname` | `Hostname` | `hostname` | `api.example.com`, `localhost`, `xn--bcher-kva.example`; not `1.2.3.4`, not Unicode | nothing is resolved |
|
|
211
|
+
| `httpUrl` | `URL` | `uri` | `https://example.com/a?b=c`, `http://localhost:3000`, `https://[2001:db8::1]/`; no other scheme, no user info | |
|
|
212
|
+
| `ipV4` | `IPv4` | `ipv4` | `192.168.0.1`: no leading zero | |
|
|
213
|
+
| `ipV6` | `IPv6` | `ipv6` | `2001:db8::1`, `::1`, `::ffff:192.0.2.1`, any case; no zone | |
|
|
214
|
+
| `ip` | `IP` | `ip` | either of the two | |
|
|
215
|
+
| `cidrV4` | `CIDRv4` | `cidr-v4` | `10.0.0.0/8`, `0.0.0.0/0` | that the address starts its block |
|
|
216
|
+
| `cidrV6` | `CIDRv6` | `cidr-v6` | `2001:db8::/32`, `::/0` | that the address starts its block |
|
|
217
|
+
| `mac` | `MAC` | `mac` | `00:1a:2b:3c:4d:5e`; not the hyphen or dotted form | |
|
|
218
|
+
| `phoneNumber` | `PhoneNumber` | `phone-number` | `+33612345678`: E.164, 7 to 15 digits, no separator | whether the number exists |
|
|
219
|
+
|
|
220
|
+
`httpUrl` is for a page's address; it is generated as
|
|
221
|
+
`zURL = z.string().regex(…)`: the pattern is the rule, and keeps to `http:`
|
|
222
|
+
and `https:` (`javascript:alert(1)` is refused). The generator leaves out
|
|
223
|
+
`z.url()` for it, which trims its input. TypeSpec's own `url` takes any
|
|
224
|
+
scheme and is generated as a bare `z.url()`.
|
|
225
|
+
|
|
226
|
+
## Locale
|
|
227
|
+
|
|
228
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
229
|
+
| --- | --- | --- | --- | --- |
|
|
230
|
+
| `countryCode` | `CountryCode` | `country-code` | `FR`, `US`: ISO 3166-1 alpha-2, uppercase | the list of assigned codes (`ZZ` passes) |
|
|
231
|
+
| `locale` | `Locale` | `locale` | `fr`, `fr-FR`, `zh-Hant-TW`, `en-US-u-ca-buddhist`: BCP 47 in canonical case, up to 255 characters | the IANA registry, the order of extensions |
|
|
232
|
+
|
|
233
|
+
## Finance
|
|
234
|
+
|
|
235
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
236
|
+
| --- | --- | --- | --- | --- |
|
|
237
|
+
| `currency` | `Currency` | `currency` | `EUR`, `USD`: ISO 4217, uppercase | the list of codes (`ZZZ` passes) |
|
|
238
|
+
| `iban` | `IBAN` | `iban` | `FR1420041010050500013M02606`: uppercase, no spaces | the mod 97 checksum, the country and its length |
|
|
239
|
+
|
|
240
|
+
## Values
|
|
241
|
+
|
|
242
|
+
`JSON`, `JSONObject` and `Void` are not scalars here. Write `unknown`,
|
|
243
|
+
`Record<unknown>` and `void`.
|
|
244
|
+
|
|
245
|
+
## In a route
|
|
246
|
+
|
|
247
|
+
The scalars work anywhere a type does: a path, a query, a header, a body.
|
|
248
|
+
|
|
249
|
+
```tsp
|
|
250
|
+
import "@typespec/http";
|
|
251
|
+
import "@nxgt/typespec";
|
|
252
|
+
|
|
253
|
+
using Http;
|
|
254
|
+
using Nxgt;
|
|
255
|
+
|
|
256
|
+
@service(#{ title: "Inventory" })
|
|
257
|
+
namespace Inventory;
|
|
258
|
+
|
|
259
|
+
model Host {
|
|
260
|
+
@visibility(Lifecycle.Read) id: uuid;
|
|
261
|
+
name: hostname;
|
|
262
|
+
address: ip;
|
|
263
|
+
network: cidrV4;
|
|
264
|
+
port: port;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@route("/hosts")
|
|
268
|
+
interface Hosts {
|
|
269
|
+
@get list(@query ipPrefix?: cidrV4): Host[];
|
|
270
|
+
@get @route("{hostId}") read(@path hostId: uuid): Host;
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
A request that breaks a pattern or a bound is answered `400` by the generated
|
|
275
|
+
validator, the same as any other schema.
|
|
276
|
+
|
|
277
|
+
## Known gaps
|
|
278
|
+
|
|
279
|
+
The pattern is an approximation. Three things a consumer meets:
|
|
280
|
+
|
|
281
|
+
- An integer scalar accepts `-0`.
|
|
282
|
+
- A scalar that depends on a list (`countryCode`, `currency`, `timeZone`, the
|
|
283
|
+
`iban` checksum, the `locale` registry) accepts a well-shaped unknown value
|
|
284
|
+
until the generator maps `x-nxgt-scalar` to `@nxgt/zod`.
|
|
285
|
+
- A declaration named like a scalar shadows it: write `Nxgt.locale`.
|
|
286
|
+
|
|
287
|
+
See also [Scalars and columns](columns.md) for `uuid`, `email` and the columns,
|
|
288
|
+
and the [troubleshooting](../troubleshooting.md) page.
|
package/docs/roadmap.md
CHANGED
|
@@ -8,6 +8,12 @@ generated by `@nxgt/openapi-codegen` in CI before it ships.
|
|
|
8
8
|
|
|
9
9
|
Nothing in progress.
|
|
10
10
|
|
|
11
|
+
## Next
|
|
12
|
+
|
|
13
|
+
- **Exact scalar rules in the generated code.** `@nxgt/openapi-codegen` maps
|
|
14
|
+
`x-nxgt-scalar` to the exact Zod schema from `@nxgt/zod`: the checksums,
|
|
15
|
+
the ISO and IANA lists and the `Intl` rules the patterns leave out.
|
|
16
|
+
|
|
11
17
|
## Later
|
|
12
18
|
|
|
13
19
|
- **`QUERY` for `search` and `query`.** The HTTP `QUERY` method, a safe
|
|
@@ -20,6 +26,22 @@ Nothing in progress.
|
|
|
20
26
|
|
|
21
27
|
## Shipped
|
|
22
28
|
|
|
29
|
+
### 0.11.0
|
|
30
|
+
|
|
31
|
+
- **Request body aliases.** `CreateRequest<T>`, `UpdateRequest<T>` and
|
|
32
|
+
`PatchRequest<T>` are TypeSpec's `Create`, `Update` and `MergePatchUpdate`
|
|
33
|
+
named `Create<T>Request`, `Update<T>Request` and `Patch<T>Request`, so the
|
|
34
|
+
generated types and schemas share one convention. `Create<T>` and
|
|
35
|
+
`MergePatchUpdate<T>` stay usable.
|
|
36
|
+
|
|
37
|
+
### 0.10.0
|
|
38
|
+
|
|
39
|
+
- **Every scalar of `@nxgt/graphql-scalars`.** The scalars, under the same
|
|
40
|
+
names, as camelCase TypeSpec scalars: each emits a component named as in
|
|
41
|
+
GraphQL, a format, a pattern or bounds approximating the rule, and
|
|
42
|
+
`x-nxgt-scalar`. `uuid` and `email` take the GraphQL rules; the components
|
|
43
|
+
`Uuid` and `Email` are now `UUID` and `EmailAddress`.
|
|
44
|
+
|
|
23
45
|
### 0.9.0
|
|
24
46
|
|
|
25
47
|
- **A `tsp init` template.** `tsp init` with the package's template
|
package/docs/troubleshooting.md
CHANGED
|
@@ -13,7 +13,7 @@ one name with `name_collision`.
|
|
|
13
13
|
optional, and the generator adds its own 400 beside it.
|
|
14
14
|
|
|
15
15
|
```tsp
|
|
16
|
-
@patch
|
|
16
|
+
@patch patchPost(@path postId: string, @body post: PatchRequest<Post>): Post | BadRequest;
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## `Couldn't resolve import "@nxgt/typespec"`
|
|
@@ -92,8 +92,9 @@ In GitHub Actions, add `oven-sh/setup-bun@v2` before the step.
|
|
|
92
92
|
|
|
93
93
|
**When:** `tsp compile` runs on a spec that declares its own model named like
|
|
94
94
|
one of the library's: `BadRequestBody` … `InternalServerErrorBody`,
|
|
95
|
-
`ValidationIssue`, `ValidationTarget`, `
|
|
96
|
-
`uuid` or `email
|
|
95
|
+
`ValidationIssue`, `ValidationTarget`, `UUID` or `EmailAddress` when the spec
|
|
96
|
+
uses `uuid` or `email` (or any other scalar, under its GraphQL name: `Latitude`,
|
|
97
|
+
`Currency`), or the page of one of its models,
|
|
97
98
|
such as `PostPage` beside `Page<Post>`, or `CommentCursorPage` beside
|
|
98
99
|
`CursorPage<Comment>`.
|
|
99
100
|
|
|
@@ -489,6 +490,97 @@ linter:
|
|
|
489
490
|
'@nxgt/typespec/error-body-shape': 'the legacy routes answer their own errors'
|
|
490
491
|
```
|
|
491
492
|
|
|
493
|
+
## `@typespec/openapi3/invalid-model-property: 'Operation' cannot be specified as a model property.`
|
|
494
|
+
|
|
495
|
+
**When:** `tsp compile` runs on a spec that has `using Nxgt` and declares, in
|
|
496
|
+
its own namespace, something named like a scalar (`locale`, `date`, `time`,
|
|
497
|
+
`port`, `hostname`…) and also uses the scalar of that name.
|
|
498
|
+
|
|
499
|
+
```tsp
|
|
500
|
+
using Nxgt;
|
|
501
|
+
|
|
502
|
+
@get op locale(): string;
|
|
503
|
+
model Page { language: locale; }
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
```text
|
|
507
|
+
error @typespec/openapi3/invalid-model-property: 'Operation' cannot be specified as a model property.
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**Why:** a declaration of the spec's own namespace shadows what `using Nxgt`
|
|
511
|
+
brings in, without a word. With an operation the name resolves to the
|
|
512
|
+
operation, and the emitter refuses it. With a model, `model date { … }` and
|
|
513
|
+
`day: date`, the property silently gets the model, a `$ref` to `date` where
|
|
514
|
+
`Date` was meant.
|
|
515
|
+
|
|
516
|
+
**Fix:** write the scalar with its namespace, or rename your declaration:
|
|
517
|
+
|
|
518
|
+
```tsp
|
|
519
|
+
model Page { language: Nxgt.locale; }
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## `` A value the GraphQL scalar refuses is accepted: `ZZ`, `ZZZ`, `Mars/Olympus`, a bad IBAN checksum ``
|
|
523
|
+
|
|
524
|
+
**When:** a request carries a value that `@nxgt/graphql-scalars` refuses but
|
|
525
|
+
the generated validator accepts and answers `2xx`: a country code that is not
|
|
526
|
+
assigned (`ZZ`), a currency (`ZZZ`), an IBAN with a wrong checksum, an ISBN
|
|
527
|
+
with a wrong check digit, a time zone (`Mars/Olympus`) or a locale that is not
|
|
528
|
+
in the registry, a JWT whose segments are not a token, an `Emoji` that is not
|
|
529
|
+
one.
|
|
530
|
+
|
|
531
|
+
**Why:** the scalar's pattern checks the shape. A list (`countryCode`,
|
|
532
|
+
`currency`, `timeZone`, `locale`) or a checksum (`iban`, ISBN) does not fit in
|
|
533
|
+
a pattern. `x-nxgt-scalar` names the exact rule, but `@nxgt/openapi-codegen`
|
|
534
|
+
does not map it to the `@nxgt/zod` schema yet ([roadmap](roadmap.md)).
|
|
535
|
+
|
|
536
|
+
**Fix:** until it does, check the value in the handler against the exact
|
|
537
|
+
rule, the list or the checksum, from `@nxgt/zod` or your own table, and answer
|
|
538
|
+
`400` as the generated validator does:
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
if (!knownCountries.has(body.country)) {
|
|
542
|
+
return c.json({ status: 400, message: 'Unknown country code' }, 400);
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
## `An integer scalar accepts -0`
|
|
547
|
+
|
|
548
|
+
**When:** a body carries `-0` for `positiveInt`, `nonNegativeInt`, `port`,
|
|
549
|
+
`safeInt` and the other integer scalars, and the validator lets it through
|
|
550
|
+
where `@nxgt/graphql-scalars` refuses it.
|
|
551
|
+
|
|
552
|
+
**Why:** `-0` is a JSON number equal to `0`, which no JSON Schema keyword can
|
|
553
|
+
tell from `0`. For `nonNegativeInt`, `nonPositiveInt`, `port`, the bound holds,
|
|
554
|
+
so the value passes (`positiveInt` and `negativeInt` refuse it by their bound).
|
|
555
|
+
|
|
556
|
+
**Fix:** normalise in the handler when the sign matters:
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
const quantity = Object.is(body.quantity, -0) ? 0 : body.quantity;
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## `UUID`, `EmailAddress`: generated `Uuid` and `Email` are gone, and an id is answered 400
|
|
563
|
+
|
|
564
|
+
**When:** after upgrading to 0.10.0, the generated code no longer exports
|
|
565
|
+
`Uuid`, `zUuid`, `Email` or `zEmail` (`Module has no exported member 'Uuid'`),
|
|
566
|
+
or a request whose id was accepted before is answered `400`.
|
|
567
|
+
|
|
568
|
+
**Why:** the schemas follow `@nxgt/graphql-scalars`' names, `UUID` and
|
|
569
|
+
`EmailAddress`, and `uuid` takes its rule: an RFC 9562 version 1 to 8 with the
|
|
570
|
+
RFC variant, or the nil or max UUID. An id of no known version
|
|
571
|
+
(`…-91d4-…`) is refused.
|
|
572
|
+
|
|
573
|
+
**Fix:** rename the imports. For an id that is a shape only, write `guid`
|
|
574
|
+
instead of `uuid`:
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
import { type UUID, zUUID, type EmailAddress, zEmailAddress } from './generated';
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
```tsp
|
|
581
|
+
model Legacy { id: guid; }
|
|
582
|
+
```
|
|
583
|
+
|
|
492
584
|
## `Unknown decorator @operationId`
|
|
493
585
|
|
|
494
586
|
**When:** a spec writes `@operationId` next to `@operationIds`.
|
package/lib/main.tsp
CHANGED
package/lib/requests.tsp
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import "@typespec/http";
|
|
2
|
+
|
|
3
|
+
using Http;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The body of a create: the properties of `T` visible on create, named
|
|
9
|
+
* `Create<T>Request` (`CreatePostRequest`). A nested model filtered the same
|
|
10
|
+
* way is named alike (`CreateAddressRequest`); one left whole keeps its name.
|
|
11
|
+
*/
|
|
12
|
+
alias CreateRequest<T extends Reflection.Model> = Create<
|
|
13
|
+
T,
|
|
14
|
+
"Create{name}Request"
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The body of a full update, as a `PUT` replaces the resource: the properties
|
|
19
|
+
* of `T` visible on update, each as optional as `T` declares it, named
|
|
20
|
+
* `Update<T>Request` (`UpdatePostRequest`).
|
|
21
|
+
*/
|
|
22
|
+
alias UpdateRequest<T extends Reflection.Model> = Update<
|
|
23
|
+
T,
|
|
24
|
+
"Update{name}Request"
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The body of a partial update, as a JSON Merge Patch
|
|
29
|
+
* (`application/merge-patch+json`): the properties of `T` visible on update,
|
|
30
|
+
* all optional, a nullable one sent as `null` to clear it, named
|
|
31
|
+
* `Patch<T>Request` (`PatchPostRequest`).
|
|
32
|
+
*/
|
|
33
|
+
alias PatchRequest<T extends Reflection.Model> = MergePatchUpdate<
|
|
34
|
+
T,
|
|
35
|
+
"Patch{name}Request"
|
|
36
|
+
>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A CSS hex color: `#` then 3, 4, 6 or 8 hexadecimal digits, in any case, kept
|
|
9
|
+
* as sent: `#ff0000`. The 4 and 8 digit forms carry an alpha.
|
|
10
|
+
*
|
|
11
|
+
* Specified by https://www.w3.org/TR/css-color-4/#hex-notation.
|
|
12
|
+
*/
|
|
13
|
+
@friendlyName("HexColorCode")
|
|
14
|
+
@format("hex-color-code")
|
|
15
|
+
@pattern("^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$")
|
|
16
|
+
@extension("x-nxgt-scalar", "HexColorCode")
|
|
17
|
+
scalar hexColorCode extends string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A CSS `hsl()` color in the comma syntax: `hsl(120, 100%, 50%)`. A hue from 0
|
|
9
|
+
* to 359 with no unit (360 is written 0), then saturation and lightness as
|
|
10
|
+
* integer percentages from 0% to 100%.
|
|
11
|
+
*
|
|
12
|
+
* Specified by https://www.w3.org/TR/css-color-4/#the-hsl-notation.
|
|
13
|
+
*/
|
|
14
|
+
@friendlyName("HSL")
|
|
15
|
+
@format("hsl")
|
|
16
|
+
@pattern("^hsl\\((?:3[0-5]\\d|[12]\\d\\d|[1-9]?\\d), (?:100|[1-9]?\\d)%, (?:100|[1-9]?\\d)%\\)$")
|
|
17
|
+
@extension("x-nxgt-scalar", "HSL")
|
|
18
|
+
scalar hsl extends string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A CSS `hsla()` color in the comma syntax: `hsla(120, 100%, 50%, 0.5)`. As
|
|
9
|
+
* `hsl` plus an alpha: `0`, `1`, or a fraction with no trailing zero and a
|
|
10
|
+
* leading `0`.
|
|
11
|
+
*
|
|
12
|
+
* Specified by https://www.w3.org/TR/css-color-4/#the-hsl-notation.
|
|
13
|
+
*/
|
|
14
|
+
@friendlyName("HSLA")
|
|
15
|
+
@format("hsla")
|
|
16
|
+
@pattern("^hsla\\((?:3[0-5]\\d|[12]\\d\\d|[1-9]?\\d), (?:100|[1-9]?\\d)%, (?:100|[1-9]?\\d)%, (?:0|1|0\\.\\d*[1-9])\\)$")
|
|
17
|
+
@extension("x-nxgt-scalar", "HSLA")
|
|
18
|
+
scalar hsla extends string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A CSS `rgb()` color in the comma syntax: `rgb(255, 0, 0)`. Three integers
|
|
9
|
+
* from 0 to 255, no leading zero, no percentage, a `, ` between them, the
|
|
10
|
+
* function name in lower case.
|
|
11
|
+
*
|
|
12
|
+
* Specified by https://www.w3.org/TR/css-color-4/#rgb-functions.
|
|
13
|
+
*/
|
|
14
|
+
@friendlyName("RGB")
|
|
15
|
+
@format("rgb")
|
|
16
|
+
@pattern("^rgb\\((?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d), (?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d), (?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\)$")
|
|
17
|
+
@extension("x-nxgt-scalar", "RGB")
|
|
18
|
+
scalar rgb extends string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A CSS `rgba()` color in the comma syntax: `rgba(255, 0, 0, 0.5)`. As `rgb`
|
|
9
|
+
* plus an alpha: `0`, `1`, or a fraction with no trailing zero and a leading
|
|
10
|
+
* `0`.
|
|
11
|
+
*
|
|
12
|
+
* Specified by https://www.w3.org/TR/css-color-4/#rgb-functions.
|
|
13
|
+
*/
|
|
14
|
+
@friendlyName("RGBA")
|
|
15
|
+
@format("rgba")
|
|
16
|
+
@pattern("^rgba\\((?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d), (?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d), (?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d), (?:0|1|0\\.\\d*[1-9])\\)$")
|
|
17
|
+
@extension("x-nxgt-scalar", "RGBA")
|
|
18
|
+
scalar rgba extends string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An RFC 3339 date-time with its offset, `Z` or `±hh:mm`, kept as sent: `2024-03-10T12:00:00+02:00`.
|
|
9
|
+
* A time with no offset names no instant and is refused, and so is the offset `-00:00`:
|
|
10
|
+
* write `Z` or `+00:00`. An impossible day is refused. The range 0000 to 9999 in UTC is
|
|
11
|
+
* not in the pattern, nor is the leap second.
|
|
12
|
+
*
|
|
13
|
+
* Specified by https://www.rfc-editor.org/rfc/rfc3339.
|
|
14
|
+
*/
|
|
15
|
+
@friendlyName("DateTime")
|
|
16
|
+
@format("date-time")
|
|
17
|
+
@pattern("^(?:\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|02-(?:0[1-9]|1\\d|2[0-8]))|(?:\\d\\d(?:0[48]|[2468][048]|[13579][26])|(?:[02468][048]|[13579][26])00)-02-29)T(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)(?:Z|\\+(?:[01]\\d|2[0-3]):[0-5]\\d|-(?!00:00)(?:[01]\\d|2[0-3]):[0-5]\\d)$")
|
|
18
|
+
@extension("x-nxgt-scalar", "DateTime")
|
|
19
|
+
scalar dateTime extends string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A calendar date, `YYYY-MM-DD`, with no time and no time zone: `2024-02-29`. An impossible
|
|
9
|
+
* day, such as `2023-02-29`, is refused.
|
|
10
|
+
*
|
|
11
|
+
* Specified by https://www.rfc-editor.org/rfc/rfc3339#section-5.6.
|
|
12
|
+
*/
|
|
13
|
+
@friendlyName("Date")
|
|
14
|
+
@format("date")
|
|
15
|
+
@pattern("^(?:\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|02-(?:0[1-9]|1\\d|2[0-8]))|(?:\\d\\d(?:0[48]|[2468][048]|[13579][26])|(?:[02468][048]|[13579][26])00)-02-29)$")
|
|
16
|
+
@extension("x-nxgt-scalar", "Date")
|
|
17
|
+
scalar date extends string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An ISO 8601 duration, kept as sent and not normalised: `P1DT2H`. Weeks are not mixed with
|
|
9
|
+
* other units, and there is no sign. Named `isoDuration` because `duration` is TypeSpec's own.
|
|
10
|
+
*
|
|
11
|
+
* Specified by https://www.iso.org/iso-8601-date-and-time-format.html.
|
|
12
|
+
*/
|
|
13
|
+
@friendlyName("Duration")
|
|
14
|
+
@format("duration")
|
|
15
|
+
@pattern("^P(?:\\d+W|(?!.*W)(?=\\d|T\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+(?:[.,]\\d+)?S)?)?)$")
|
|
16
|
+
@extension("x-nxgt-scalar", "Duration")
|
|
17
|
+
scalar isoDuration extends string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A date and a time on the wall clock, with no offset, not even `Z`: `2024-03-10T10:15:30`.
|
|
9
|
+
* The seconds are optional. An impossible day is refused.
|
|
10
|
+
*/
|
|
11
|
+
@friendlyName("LocalDateTime")
|
|
12
|
+
@format("local-date-time")
|
|
13
|
+
@pattern("^(?:\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|02-(?:0[1-9]|1\\d|2[0-8]))|(?:\\d\\d(?:0[48]|[2468][048]|[13579][26])|(?:[02468][048]|[13579][26])00)-02-29)T(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$")
|
|
14
|
+
@extension("x-nxgt-scalar", "LocalDateTime")
|
|
15
|
+
scalar localDateTime extends string;
|