typespec-rust-emitter 0.13.0 → 0.13.1
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/CHANGELOG.md +7 -0
- package/dist/src/generator/etag_router.d.ts +3 -5
- package/dist/src/generator/etag_router.js +7 -54
- package/dist/src/generator/etag_router.js.map +1 -1
- package/dist/src/generator/router.js +6 -2
- package/dist/src/generator/router.js.map +1 -1
- package/dist/test/etag_cache.test.js +24 -0
- package/dist/test/etag_cache.test.js.map +1 -1
- package/example/main.tsp +4 -1
- package/example/output-rust/src/generated/server.rs +44 -14
- package/example/output-rust/src/main.rs +6 -1
- package/package.json +1 -1
- package/src/generator/etag_router.ts +11 -61
- package/src/generator/router.ts +6 -2
- package/test/etag_cache.test.ts +35 -0
- package/test/golden/etag_cache/server.rs +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.13.1] - 2026-05-12
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **@etagCache with @useAuth**: Cached handlers now preserve auth claims and forward `cache` in the correct argument order.
|
|
13
|
+
- **Cached handler generation**: ETag-aware handlers now reuse the same extractor list as normal routes, which keeps path and auth extraction aligned.
|
|
14
|
+
|
|
8
15
|
## [0.13.0] - 2026-05-12
|
|
9
16
|
|
|
10
17
|
### Added
|
|
@@ -12,19 +12,17 @@ export declare function generateEtagCacheTrait(): string;
|
|
|
12
12
|
*
|
|
13
13
|
* @param handlerFnName snake_case name, e.g. "articles_get_article"
|
|
14
14
|
* @param cacheKey format string for the cache key, e.g. "articles_get_article:{id}"
|
|
15
|
-
* @param
|
|
15
|
+
* @param extractorLines axum extractors needed by the operation
|
|
16
16
|
* @param serverArgsStr comma-separated args forwarded to the trait method
|
|
17
17
|
* @param responseName PascalCase name of the response enum, e.g. "ArticlesGetArticleResponse"
|
|
18
18
|
*/
|
|
19
19
|
export declare function generateEtagHandler(opts: {
|
|
20
20
|
handlerFnName: string;
|
|
21
21
|
cacheKey: string;
|
|
22
|
-
|
|
23
|
-
rustName: string;
|
|
24
|
-
rustType: string;
|
|
25
|
-
}[];
|
|
22
|
+
extractorLines: string[];
|
|
26
23
|
serverArgsStr: string;
|
|
27
24
|
responseName: string;
|
|
28
25
|
etagKey?: string;
|
|
29
26
|
cacheControl?: string;
|
|
27
|
+
serviceBinding?: string;
|
|
30
28
|
}): string;
|
|
@@ -22,62 +22,14 @@ pub trait EtagCache {
|
|
|
22
22
|
*
|
|
23
23
|
* @param handlerFnName snake_case name, e.g. "articles_get_article"
|
|
24
24
|
* @param cacheKey format string for the cache key, e.g. "articles_get_article:{id}"
|
|
25
|
-
* @param
|
|
25
|
+
* @param extractorLines axum extractors needed by the operation
|
|
26
26
|
* @param serverArgsStr comma-separated args forwarded to the trait method
|
|
27
27
|
* @param responseName PascalCase name of the response enum, e.g. "ArticlesGetArticleResponse"
|
|
28
28
|
*/
|
|
29
29
|
export function generateEtagHandler(opts) {
|
|
30
|
-
const { handlerFnName, cacheKey,
|
|
31
|
-
// Build Path extractor
|
|
32
|
-
let pathExtractor = "";
|
|
33
|
-
if (pathParams.length === 1) {
|
|
34
|
-
pathExtractor = ` Path(${pathParams[0].rustName}): Path<${pathParams[0].rustType}>,\n`;
|
|
35
|
-
}
|
|
36
|
-
else if (pathParams.length > 1) {
|
|
37
|
-
const names = pathParams.map((p) => p.rustName).join(", ");
|
|
38
|
-
const types = pathParams.map((p) => p.rustType).join(", ");
|
|
39
|
-
pathExtractor = ` Path((${names})): Path<(${types})>,\n`;
|
|
40
|
-
}
|
|
30
|
+
const { handlerFnName, cacheKey, extractorLines, serverArgsStr, etagKey, cacheControl, serviceBinding = "service", } = opts;
|
|
41
31
|
const effectiveCacheKey = etagKey ? `"${etagKey}"` : `format!("${cacheKey}")`;
|
|
42
|
-
|
|
43
|
-
Ok(response) => {
|
|
44
|
-
let res = response.into_response();
|
|
45
|
-
`;
|
|
46
|
-
const hasHeaders = !!cacheControl; // Currently only cacheControl adds headers here, but etag check also does.
|
|
47
|
-
// Wait, the etag check logic is different.
|
|
48
|
-
if (cacheControl) {
|
|
49
|
-
responseLogic = ` match result {
|
|
50
|
-
Ok(response) => {
|
|
51
|
-
let mut res = response.into_response();
|
|
52
|
-
res.headers_mut().insert(
|
|
53
|
-
axum::http::header::CACHE_CONTROL,
|
|
54
|
-
axum::http::HeaderValue::from_static("${cacheControl}"),
|
|
55
|
-
);
|
|
56
|
-
`;
|
|
57
|
-
}
|
|
58
|
-
// We should also potentially add the ETag header if we have one in cache
|
|
59
|
-
// But wait, the ETag value in cache is only valid if the response is successful and matches the cached version.
|
|
60
|
-
// Usually, the business logic would set the ETag in the cache.
|
|
61
|
-
responseLogic += ` if let Some(stored_etag) = cache.get(cache_key.as_ref()) {
|
|
62
|
-
let mut res = res; // Ensure it's mutable if we need to add etag
|
|
63
|
-
let mut res = if res.headers().get(axum::http::header::ETAG).is_none() {
|
|
64
|
-
let mut res = res;
|
|
65
|
-
res.headers_mut().insert(
|
|
66
|
-
axum::http::header::ETAG,
|
|
67
|
-
axum::http::HeaderValue::from_str(&stored_etag).unwrap(),
|
|
68
|
-
);
|
|
69
|
-
res
|
|
70
|
-
} else {
|
|
71
|
-
res
|
|
72
|
-
};
|
|
73
|
-
res
|
|
74
|
-
} else {
|
|
75
|
-
res
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
`;
|
|
79
|
-
// Actually, let's keep it simpler for now to avoid complexity in the template.
|
|
80
|
-
// I'll just fix the let_and_return for the common case.
|
|
32
|
+
const operationExtractors = extractorLines.length > 0 ? `${extractorLines.join("\n")}\n` : "";
|
|
81
33
|
const finalResponseLogic = ` match result {
|
|
82
34
|
Ok(response) => {
|
|
83
35
|
let mut res = response.into_response();
|
|
@@ -97,12 +49,13 @@ export function generateEtagHandler(opts) {
|
|
|
97
49
|
.into_response(),
|
|
98
50
|
}`;
|
|
99
51
|
return `pub async fn ${handlerFnName}_handler<S, C>(
|
|
100
|
-
axum::extract::State(
|
|
52
|
+
axum::extract::State(${serviceBinding}): axum::extract::State<S>,
|
|
101
53
|
axum::extract::State(cache): axum::extract::State<C>,
|
|
102
54
|
if_none_match: Option<axum_extra::TypedHeader<axum_extra::headers::IfNoneMatch>>,
|
|
103
|
-
${
|
|
55
|
+
${operationExtractors}) -> impl axum::response::IntoResponse
|
|
104
56
|
where
|
|
105
57
|
S: Server + Send + Sync + 'static,
|
|
58
|
+
S::Claims: Send + Sync + Clone + 'static,
|
|
106
59
|
C: EtagCache + Clone + Send + Sync + 'static,
|
|
107
60
|
{
|
|
108
61
|
// Check If-None-Match against the cache
|
|
@@ -116,7 +69,7 @@ where
|
|
|
116
69
|
}
|
|
117
70
|
}
|
|
118
71
|
// Forward to business logic
|
|
119
|
-
let result =
|
|
72
|
+
let result = ${serviceBinding}.${handlerFnName}(${serverArgsStr}).await;
|
|
120
73
|
${finalResponseLogic}
|
|
121
74
|
}`;
|
|
122
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"etag_router.js","sourceRoot":"","sources":["../../../src/generator/etag_router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO;;;;;;;;CAQR,CAAC;AACF,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,
|
|
1
|
+
{"version":3,"file":"etag_router.js","sourceRoot":"","sources":["../../../src/generator/etag_router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO;;;;;;;;CAQR,CAAC;AACF,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,IASnC;IACC,MAAM,EACJ,aAAa,EACb,QAAQ,EACR,cAAc,EACd,aAAa,EACb,OAAO,EACP,YAAY,EACZ,cAAc,GAAG,SAAS,GAC3B,GAAG,IAAI,CAAC;IAET,MAAM,iBAAiB,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,YAAY,QAAQ,IAAI,CAAC;IAC9E,MAAM,mBAAmB,GACvB,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAEpE,MAAM,kBAAkB,GAAG;;;;;;;;;cASf,YAAY,CAAC,CAAC,CAAC,qGAAqG,YAAY,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;MAQnJ,CAAC;IAEL,OAAO,gBAAgB,aAAa;2BACX,cAAc;;;EAGvC,mBAAmB;;;;;;;sBAOC,iBAAiB;;;;;cAKzB,YAAY,CAAC,CAAC,CAAC,qGAAqG,YAAY,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;mBAKtI,cAAc,IAAI,aAAa,IAAI,aAAa;EACjE,kBAAkB;EAClB,CAAC;AACH,CAAC"}
|
|
@@ -77,14 +77,18 @@ export function generateRouter(program, namespaceGroups, anonymousEnums) {
|
|
|
77
77
|
const serverArgsStr = serverArgs.join(", ");
|
|
78
78
|
if (hasEtagCache(program, op)) {
|
|
79
79
|
const cacheKey = `${handlerFnName}:${pathParams.map((p) => `{${p.rustName}}`).join(":")}`;
|
|
80
|
+
const cachedServerArgs = isProtected
|
|
81
|
+
? [serverArgs[0], "&cache", ...serverArgs.slice(1)]
|
|
82
|
+
: ["&cache", ...serverArgs];
|
|
80
83
|
const code = generateEtagHandler({
|
|
81
84
|
handlerFnName,
|
|
82
85
|
cacheKey,
|
|
83
|
-
|
|
84
|
-
serverArgsStr,
|
|
86
|
+
extractorLines,
|
|
87
|
+
serverArgsStr: cachedServerArgs.join(", "),
|
|
85
88
|
responseName: `${nsName}${toPascalCase(opInfo.name)}Response`,
|
|
86
89
|
etagKey: opInfo.etagKey,
|
|
87
90
|
cacheControl: opInfo.cacheControl,
|
|
91
|
+
serviceBinding,
|
|
88
92
|
});
|
|
89
93
|
handlers.push(code);
|
|
90
94
|
const routePath = `"${opInfo.path}"`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/generator/router.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/generator/router.ts"],"names":[],"mappings":"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"}
|
|
@@ -54,6 +54,30 @@ describe("@etagCache decorator", () => {
|
|
|
54
54
|
strictEqual(server.includes('axum::http::header::CACHE_CONTROL'), true);
|
|
55
55
|
strictEqual(server.includes('"public"'), true);
|
|
56
56
|
});
|
|
57
|
+
it("orders cache after claims when combined with @useAuth", async () => {
|
|
58
|
+
const spec = `
|
|
59
|
+
import "@typespec/http";
|
|
60
|
+
import "typespec-rust-emitter";
|
|
61
|
+
using TypeSpec.Http;
|
|
62
|
+
|
|
63
|
+
model Article { id: string; title: string; }
|
|
64
|
+
|
|
65
|
+
@route("/articles")
|
|
66
|
+
namespace Articles {
|
|
67
|
+
@etagCache("article-list")
|
|
68
|
+
@useAuth(BearerAuth)
|
|
69
|
+
@get
|
|
70
|
+
op getArticle(@path id: string): {
|
|
71
|
+
@statusCode statusCode: 200;
|
|
72
|
+
@body body: Article;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
const results = await emit(spec);
|
|
77
|
+
const server = results["server.rs"];
|
|
78
|
+
strictEqual(server.includes("async fn articles_get_article<C: EtagCache + Send + Sync>(\n &self, claims: Self::Claims, cache: &C, id: String"), true);
|
|
79
|
+
strictEqual(server.includes("let result = service.articles_get_article(claims, &cache, id).await;"), true);
|
|
80
|
+
});
|
|
57
81
|
it("matches golden file", async () => {
|
|
58
82
|
const results = await emit(ETAG_SPEC);
|
|
59
83
|
compareWithGolden(results, "etag_cache", "server.rs");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"etag_cache.test.js","sourceRoot":"","sources":["../../test/etag_cache.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;CAkBjB,CAAC;AAEF,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,IAAI,CAAC,CAAC;QAC1D,WAAW,CACT,MAAM,CAAC,QAAQ,CAAC,4CAA4C,CAAC,EAC7D,IAAI,CACL,CAAC;QACF,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,sCAAsC,CAAC,EAAE,IAAI,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,sCAAsC,CAAC,EAAE,IAAI,CAAC,CAAC;QAC3E,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,+BAA+B,CAAC,EAAE,IAAI,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,IAAI,GAAG;;;;;;;;;;;;KAYZ,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,2BAA2B,CAAC,EAAE,IAAI,CAAC,CAAC;QAChE,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,mCAAmC,CAAC,EAAE,IAAI,CAAC,CAAC;QACxE,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,KAAK,IAAI,EAAE;QACnC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"etag_cache.test.js","sourceRoot":"","sources":["../../test/etag_cache.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;CAkBjB,CAAC;AAEF,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,IAAI,CAAC,CAAC;QAC1D,WAAW,CACT,MAAM,CAAC,QAAQ,CAAC,4CAA4C,CAAC,EAC7D,IAAI,CACL,CAAC;QACF,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,sCAAsC,CAAC,EAAE,IAAI,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,sCAAsC,CAAC,EAAE,IAAI,CAAC,CAAC;QAC3E,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,+BAA+B,CAAC,EAAE,IAAI,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,IAAI,GAAG;;;;;;;;;;;;KAYZ,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,2BAA2B,CAAC,EAAE,IAAI,CAAC,CAAC;QAChE,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,mCAAmC,CAAC,EAAE,IAAI,CAAC,CAAC;QACxE,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;KAiBZ,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,WAAW,CACT,MAAM,CAAC,QAAQ,CACb,wHAAwH,CACzH,EACD,IAAI,CACL,CAAC;QACF,WAAW,CACT,MAAM,CAAC,QAAQ,CACb,sEAAsE,CACvE,EACD,IAAI,CACL,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,KAAK,IAAI,EAAE;QACnC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/example/main.tsp
CHANGED
|
@@ -43,13 +43,15 @@ model Item {
|
|
|
43
43
|
|
|
44
44
|
@route("/items")
|
|
45
45
|
namespace Items {
|
|
46
|
-
@
|
|
46
|
+
@etagCache("article-list")
|
|
47
|
+
@cacheControl("public, max-age=3600")
|
|
47
48
|
@get
|
|
48
49
|
op list(): {
|
|
49
50
|
@statusCode statusCode: 200;
|
|
50
51
|
@body items: Item[];
|
|
51
52
|
};
|
|
52
53
|
|
|
54
|
+
@cacheControl("no-cache")
|
|
53
55
|
@get
|
|
54
56
|
@route("/{id}")
|
|
55
57
|
op getItem(@path id: string): {
|
|
@@ -81,6 +83,7 @@ model Article {
|
|
|
81
83
|
namespace Articles {
|
|
82
84
|
@etagCache("article-list")
|
|
83
85
|
@cacheControl("public, max-age=3600")
|
|
86
|
+
@useAuth(BearerAuth)
|
|
84
87
|
@get
|
|
85
88
|
op getArticle(@path id: string): {
|
|
86
89
|
@statusCode statusCode: 200;
|
|
@@ -28,12 +28,14 @@ pub trait Server: Send + Sync {
|
|
|
28
28
|
|
|
29
29
|
async fn events_accounts_events(&self, account_id: String) -> Result<EventsAccountsEventsResponse>;
|
|
30
30
|
async fn pets_list(&self, first_query: String, second_query: String) -> Result<PetsListResponse>;
|
|
31
|
-
async fn items_list
|
|
31
|
+
async fn items_list<C: EtagCache + Send + Sync>(
|
|
32
|
+
&self, cache: &C
|
|
33
|
+
) -> Result<ItemsListResponse>;
|
|
32
34
|
async fn items_get_item(&self, id: String) -> Result<ItemsGetItemResponse>;
|
|
33
35
|
async fn items_create_item(&mut self, body: Item) -> Result<ItemsCreateItemResponse>;
|
|
34
36
|
async fn items_update_item(&mut self, id: String, body: Item) -> Result<ItemsUpdateItemResponse>;
|
|
35
37
|
async fn articles_get_article<C: EtagCache + Send + Sync>(
|
|
36
|
-
&self, cache: &C, id: String
|
|
38
|
+
&self, claims: Self::Claims, cache: &C, id: String
|
|
37
39
|
) -> Result<ArticlesGetArticleResponse>;
|
|
38
40
|
async fn consuming_consume_and_delete(self, id: String) -> Result<ConsumingConsumeAndDeleteResponse>;
|
|
39
41
|
async fn consuming_upload(&self, account_id: uuid::Uuid, body: Multipart) -> Result<ConsumingUploadResponse>;
|
|
@@ -235,22 +237,38 @@ where
|
|
|
235
237
|
}
|
|
236
238
|
}
|
|
237
239
|
|
|
238
|
-
pub async fn items_list_handler<S>(
|
|
240
|
+
pub async fn items_list_handler<S, C>(
|
|
239
241
|
axum::extract::State(service): axum::extract::State<S>,
|
|
240
|
-
|
|
242
|
+
axum::extract::State(cache): axum::extract::State<C>,
|
|
243
|
+
if_none_match: Option<axum_extra::TypedHeader<axum_extra::headers::IfNoneMatch>>,
|
|
241
244
|
) -> impl axum::response::IntoResponse
|
|
242
245
|
where
|
|
243
|
-
S: Server
|
|
246
|
+
S: Server + Send + Sync + 'static,
|
|
244
247
|
S::Claims: Send + Sync + Clone + 'static,
|
|
248
|
+
C: EtagCache + Clone + Send + Sync + 'static,
|
|
245
249
|
{
|
|
246
|
-
|
|
250
|
+
// Check If-None-Match against the cache
|
|
251
|
+
let cache_key = "article-list";
|
|
252
|
+
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) = (cache.get(cache_key.as_ref()), if_none_match) {
|
|
253
|
+
// If the client's ETag matches, respond 304 immediately
|
|
254
|
+
if stored_etag == format!("{:?}", inm).trim_matches('"') {
|
|
255
|
+
let mut res = axum::http::StatusCode::NOT_MODIFIED.into_response();
|
|
256
|
+
res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("public, max-age=3600"));
|
|
257
|
+
return res;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Forward to business logic
|
|
261
|
+
let result = service.items_list(&cache).await;
|
|
247
262
|
match result {
|
|
248
263
|
Ok(response) => {
|
|
249
264
|
let mut res = response.into_response();
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
265
|
+
if let Some(stored_etag) = cache.get(cache_key.as_ref()) {
|
|
266
|
+
res.headers_mut().insert(
|
|
267
|
+
axum::http::header::ETAG,
|
|
268
|
+
axum::http::HeaderValue::from_str(&stored_etag).unwrap(),
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("public, max-age=3600"));
|
|
254
272
|
res
|
|
255
273
|
}
|
|
256
274
|
Err(e) => (
|
|
@@ -271,7 +289,14 @@ where
|
|
|
271
289
|
{
|
|
272
290
|
let result = service.items_get_item(id).await;
|
|
273
291
|
match result {
|
|
274
|
-
Ok(response) =>
|
|
292
|
+
Ok(response) => {
|
|
293
|
+
let mut res = response.into_response();
|
|
294
|
+
res.headers_mut().insert(
|
|
295
|
+
axum::http::header::CACHE_CONTROL,
|
|
296
|
+
axum::http::HeaderValue::from_static("no-cache"),
|
|
297
|
+
);
|
|
298
|
+
res
|
|
299
|
+
}
|
|
275
300
|
Err(e) => (
|
|
276
301
|
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
|
277
302
|
format!("Internal error: {e}"),
|
|
@@ -323,10 +348,12 @@ pub async fn articles_get_article_handler<S, C>(
|
|
|
323
348
|
axum::extract::State(service): axum::extract::State<S>,
|
|
324
349
|
axum::extract::State(cache): axum::extract::State<C>,
|
|
325
350
|
if_none_match: Option<axum_extra::TypedHeader<axum_extra::headers::IfNoneMatch>>,
|
|
351
|
+
Extension(claims): Extension<S::Claims>,
|
|
326
352
|
Path(id): Path<String>,
|
|
327
353
|
) -> impl axum::response::IntoResponse
|
|
328
354
|
where
|
|
329
355
|
S: Server + Send + Sync + 'static,
|
|
356
|
+
S::Claims: Send + Sync + Clone + 'static,
|
|
330
357
|
C: EtagCache + Clone + Send + Sync + 'static,
|
|
331
358
|
{
|
|
332
359
|
// Check If-None-Match against the cache
|
|
@@ -340,7 +367,7 @@ where
|
|
|
340
367
|
}
|
|
341
368
|
}
|
|
342
369
|
// Forward to business logic
|
|
343
|
-
let result = service.articles_get_article(&cache, id).await;
|
|
370
|
+
let result = service.articles_get_article(claims, &cache, id).await;
|
|
344
371
|
match result {
|
|
345
372
|
Ok(response) => {
|
|
346
373
|
let mut res = response.into_response();
|
|
@@ -413,13 +440,16 @@ where
|
|
|
413
440
|
let public = Router::new()
|
|
414
441
|
.route("/events/{accountId}", get(events_accounts_events_handler::<S>))
|
|
415
442
|
.route("/pets", get(pets_list_handler::<S>))
|
|
416
|
-
.route("/items", get(items_list_handler::<S>))
|
|
443
|
+
.route("/items", get(items_list_handler::<S, C>))
|
|
417
444
|
.route("/items/{id}", get(items_get_item_handler::<S>))
|
|
418
445
|
.route("/items", post(items_create_item_handler::<S>))
|
|
419
446
|
.route("/items", put(items_update_item_handler::<S>))
|
|
420
|
-
.route("/articles", get(articles_get_article_handler::<S, C>))
|
|
421
447
|
.route("/consuming", post(consuming_upload_handler::<S>))
|
|
422
448
|
;
|
|
423
449
|
router = router.merge(public);
|
|
450
|
+
let protected = Router::new()
|
|
451
|
+
.route("/articles", get(articles_get_article_handler::<S, C>))
|
|
452
|
+
;
|
|
453
|
+
router = router.merge(middleware(protected));
|
|
424
454
|
router.with_state(service)
|
|
425
455
|
}
|
|
@@ -67,7 +67,11 @@ impl Server for AppState {
|
|
|
67
67
|
])))
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
async fn items_list
|
|
70
|
+
async fn items_list<C: EtagCache + Send + Sync>(
|
|
71
|
+
&self,
|
|
72
|
+
cache: &C,
|
|
73
|
+
) -> eyre::Result<ItemsListResponse> {
|
|
74
|
+
cache.set("article-list", "\"items-etag\"");
|
|
71
75
|
Ok(ItemsListResponse::Ok(Json(vec![generated::types::Item {
|
|
72
76
|
name: "list-item".to_string(),
|
|
73
77
|
value: 123,
|
|
@@ -104,6 +108,7 @@ impl Server for AppState {
|
|
|
104
108
|
|
|
105
109
|
async fn articles_get_article<C: EtagCache + Send + Sync>(
|
|
106
110
|
&self,
|
|
111
|
+
_claims: Self::Claims,
|
|
107
112
|
cache: &C,
|
|
108
113
|
id: String,
|
|
109
114
|
) -> eyre::Result<ArticlesGetArticleResponse> {
|
package/package.json
CHANGED
|
@@ -23,84 +23,33 @@ pub trait EtagCache {
|
|
|
23
23
|
*
|
|
24
24
|
* @param handlerFnName snake_case name, e.g. "articles_get_article"
|
|
25
25
|
* @param cacheKey format string for the cache key, e.g. "articles_get_article:{id}"
|
|
26
|
-
* @param
|
|
26
|
+
* @param extractorLines axum extractors needed by the operation
|
|
27
27
|
* @param serverArgsStr comma-separated args forwarded to the trait method
|
|
28
28
|
* @param responseName PascalCase name of the response enum, e.g. "ArticlesGetArticleResponse"
|
|
29
29
|
*/
|
|
30
30
|
export function generateEtagHandler(opts: {
|
|
31
31
|
handlerFnName: string;
|
|
32
32
|
cacheKey: string;
|
|
33
|
-
|
|
33
|
+
extractorLines: string[];
|
|
34
34
|
serverArgsStr: string;
|
|
35
35
|
responseName: string;
|
|
36
36
|
etagKey?: string;
|
|
37
37
|
cacheControl?: string;
|
|
38
|
+
serviceBinding?: string;
|
|
38
39
|
}): string {
|
|
39
40
|
const {
|
|
40
41
|
handlerFnName,
|
|
41
42
|
cacheKey,
|
|
42
|
-
|
|
43
|
+
extractorLines,
|
|
43
44
|
serverArgsStr,
|
|
44
45
|
etagKey,
|
|
45
46
|
cacheControl,
|
|
47
|
+
serviceBinding = "service",
|
|
46
48
|
} = opts;
|
|
47
49
|
|
|
48
|
-
// Build Path extractor
|
|
49
|
-
let pathExtractor = "";
|
|
50
|
-
if (pathParams.length === 1) {
|
|
51
|
-
pathExtractor = ` Path(${pathParams[0].rustName}): Path<${pathParams[0].rustType}>,\n`;
|
|
52
|
-
} else if (pathParams.length > 1) {
|
|
53
|
-
const names = pathParams.map((p) => p.rustName).join(", ");
|
|
54
|
-
const types = pathParams.map((p) => p.rustType).join(", ");
|
|
55
|
-
pathExtractor = ` Path((${names})): Path<(${types})>,\n`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
50
|
const effectiveCacheKey = etagKey ? `"${etagKey}"` : `format!("${cacheKey}")`;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Ok(response) => {
|
|
62
|
-
let res = response.into_response();
|
|
63
|
-
`;
|
|
64
|
-
|
|
65
|
-
const hasHeaders = !!cacheControl; // Currently only cacheControl adds headers here, but etag check also does.
|
|
66
|
-
// Wait, the etag check logic is different.
|
|
67
|
-
|
|
68
|
-
if (cacheControl) {
|
|
69
|
-
responseLogic = ` match result {
|
|
70
|
-
Ok(response) => {
|
|
71
|
-
let mut res = response.into_response();
|
|
72
|
-
res.headers_mut().insert(
|
|
73
|
-
axum::http::header::CACHE_CONTROL,
|
|
74
|
-
axum::http::HeaderValue::from_static("${cacheControl}"),
|
|
75
|
-
);
|
|
76
|
-
`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// We should also potentially add the ETag header if we have one in cache
|
|
80
|
-
// But wait, the ETag value in cache is only valid if the response is successful and matches the cached version.
|
|
81
|
-
// Usually, the business logic would set the ETag in the cache.
|
|
82
|
-
|
|
83
|
-
responseLogic += ` if let Some(stored_etag) = cache.get(cache_key.as_ref()) {
|
|
84
|
-
let mut res = res; // Ensure it's mutable if we need to add etag
|
|
85
|
-
let mut res = if res.headers().get(axum::http::header::ETAG).is_none() {
|
|
86
|
-
let mut res = res;
|
|
87
|
-
res.headers_mut().insert(
|
|
88
|
-
axum::http::header::ETAG,
|
|
89
|
-
axum::http::HeaderValue::from_str(&stored_etag).unwrap(),
|
|
90
|
-
);
|
|
91
|
-
res
|
|
92
|
-
} else {
|
|
93
|
-
res
|
|
94
|
-
};
|
|
95
|
-
res
|
|
96
|
-
} else {
|
|
97
|
-
res
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
`;
|
|
101
|
-
|
|
102
|
-
// Actually, let's keep it simpler for now to avoid complexity in the template.
|
|
103
|
-
// I'll just fix the let_and_return for the common case.
|
|
51
|
+
const operationExtractors =
|
|
52
|
+
extractorLines.length > 0 ? `${extractorLines.join("\n")}\n` : "";
|
|
104
53
|
|
|
105
54
|
const finalResponseLogic = ` match result {
|
|
106
55
|
Ok(response) => {
|
|
@@ -122,12 +71,13 @@ export function generateEtagHandler(opts: {
|
|
|
122
71
|
}`;
|
|
123
72
|
|
|
124
73
|
return `pub async fn ${handlerFnName}_handler<S, C>(
|
|
125
|
-
axum::extract::State(
|
|
74
|
+
axum::extract::State(${serviceBinding}): axum::extract::State<S>,
|
|
126
75
|
axum::extract::State(cache): axum::extract::State<C>,
|
|
127
76
|
if_none_match: Option<axum_extra::TypedHeader<axum_extra::headers::IfNoneMatch>>,
|
|
128
|
-
${
|
|
77
|
+
${operationExtractors}) -> impl axum::response::IntoResponse
|
|
129
78
|
where
|
|
130
79
|
S: Server + Send + Sync + 'static,
|
|
80
|
+
S::Claims: Send + Sync + Clone + 'static,
|
|
131
81
|
C: EtagCache + Clone + Send + Sync + 'static,
|
|
132
82
|
{
|
|
133
83
|
// Check If-None-Match against the cache
|
|
@@ -141,7 +91,7 @@ where
|
|
|
141
91
|
}
|
|
142
92
|
}
|
|
143
93
|
// Forward to business logic
|
|
144
|
-
let result =
|
|
94
|
+
let result = ${serviceBinding}.${handlerFnName}(${serverArgsStr}).await;
|
|
145
95
|
${finalResponseLogic}
|
|
146
96
|
}`;
|
|
147
97
|
}
|
package/src/generator/router.ts
CHANGED
|
@@ -118,14 +118,18 @@ export function generateRouter(
|
|
|
118
118
|
|
|
119
119
|
if (hasEtagCache(program, op)) {
|
|
120
120
|
const cacheKey = `${handlerFnName}:${pathParams.map((p) => `{${p.rustName}}`).join(":")}`;
|
|
121
|
+
const cachedServerArgs = isProtected
|
|
122
|
+
? [serverArgs[0], "&cache", ...serverArgs.slice(1)]
|
|
123
|
+
: ["&cache", ...serverArgs];
|
|
121
124
|
const code = generateEtagHandler({
|
|
122
125
|
handlerFnName,
|
|
123
126
|
cacheKey,
|
|
124
|
-
|
|
125
|
-
serverArgsStr,
|
|
127
|
+
extractorLines,
|
|
128
|
+
serverArgsStr: cachedServerArgs.join(", "),
|
|
126
129
|
responseName: `${nsName}${toPascalCase(opInfo.name)}Response`,
|
|
127
130
|
etagKey: opInfo.etagKey,
|
|
128
131
|
cacheControl: opInfo.cacheControl,
|
|
132
|
+
serviceBinding,
|
|
129
133
|
});
|
|
130
134
|
handlers.push(code);
|
|
131
135
|
const routePath = `"${opInfo.path}"`;
|
package/test/etag_cache.test.ts
CHANGED
|
@@ -62,6 +62,41 @@ describe("@etagCache decorator", () => {
|
|
|
62
62
|
strictEqual(server.includes('"public"'), true);
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
it("orders cache after claims when combined with @useAuth", async () => {
|
|
66
|
+
const spec = `
|
|
67
|
+
import "@typespec/http";
|
|
68
|
+
import "typespec-rust-emitter";
|
|
69
|
+
using TypeSpec.Http;
|
|
70
|
+
|
|
71
|
+
model Article { id: string; title: string; }
|
|
72
|
+
|
|
73
|
+
@route("/articles")
|
|
74
|
+
namespace Articles {
|
|
75
|
+
@etagCache("article-list")
|
|
76
|
+
@useAuth(BearerAuth)
|
|
77
|
+
@get
|
|
78
|
+
op getArticle(@path id: string): {
|
|
79
|
+
@statusCode statusCode: 200;
|
|
80
|
+
@body body: Article;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
const results = await emit(spec);
|
|
85
|
+
const server = results["server.rs"];
|
|
86
|
+
strictEqual(
|
|
87
|
+
server.includes(
|
|
88
|
+
"async fn articles_get_article<C: EtagCache + Send + Sync>(\n &self, claims: Self::Claims, cache: &C, id: String",
|
|
89
|
+
),
|
|
90
|
+
true,
|
|
91
|
+
);
|
|
92
|
+
strictEqual(
|
|
93
|
+
server.includes(
|
|
94
|
+
"let result = service.articles_get_article(claims, &cache, id).await;",
|
|
95
|
+
),
|
|
96
|
+
true,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
65
100
|
it("matches golden file", async () => {
|
|
66
101
|
const results = await emit(ETAG_SPEC);
|
|
67
102
|
compareWithGolden(results, "etag_cache", "server.rs");
|
|
@@ -59,6 +59,7 @@ pub async fn articles_get_article_handler<S, C>(
|
|
|
59
59
|
) -> impl axum::response::IntoResponse
|
|
60
60
|
where
|
|
61
61
|
S: Server + Send + Sync + 'static,
|
|
62
|
+
S::Claims: Send + Sync + Clone + 'static,
|
|
62
63
|
C: EtagCache + Clone + Send + Sync + 'static,
|
|
63
64
|
{
|
|
64
65
|
// Check If-None-Match against the cache
|