typespec-rust-emitter 0.13.1 → 0.14.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/.prettierignore +2 -0
- package/CHANGELOG.md +8 -0
- package/dist/src/generator/etag_router.d.ts +3 -2
- package/dist/src/generator/etag_router.js +53 -17
- package/dist/src/generator/etag_router.js.map +1 -1
- package/dist/src/generator/router.js +2 -3
- package/dist/src/generator/router.js.map +1 -1
- package/dist/src/generator/server_trait.js.map +1 -1
- package/dist/src/parser/operations.js.map +1 -1
- package/dist/test/etag_cache.test.js +41 -4
- package/dist/test/etag_cache.test.js.map +1 -1
- package/example/main.tsp +24 -0
- package/example/output-rust/src/generated/server.rs +154 -27
- package/example/output-rust/src/main.rs +49 -6
- package/package.json +1 -1
- package/scripts/update-golden.js +12 -12
- package/src/generator/etag_router.ts +68 -18
- package/src/generator/router.ts +2 -3
- package/src/generator/server_trait.ts +5 -1
- package/src/parser/operations.ts +11 -3
- package/test/etag_cache.test.ts +58 -4
- package/test/golden/etag_cache/server.rs +15 -14
package/.prettierignore
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ 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.14.0] - 2026-05-12
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **ETag cache generation**: `@etagCache` now emits an async cache trait, typed `If-None-Match` comparison, and safer cached header handling.
|
|
13
|
+
- **Cache key scoping**: Generated ETag keys now interpolate route parameters and append path params by default when the decorator key is static.
|
|
14
|
+
- **Example coverage**: The demo spec now includes route-parameter interpolation and multi-parameter cache-key examples.
|
|
15
|
+
|
|
8
16
|
## [0.13.1] - 2026-05-12
|
|
9
17
|
|
|
10
18
|
### Fixed
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ParameterInfo } from "../models/types.js";
|
|
1
2
|
/**
|
|
2
3
|
* generateEtagCacheTrait
|
|
3
4
|
*
|
|
@@ -11,14 +12,14 @@ export declare function generateEtagCacheTrait(): string;
|
|
|
11
12
|
* Returns the Rust source for a single ETag-aware axum handler.
|
|
12
13
|
*
|
|
13
14
|
* @param handlerFnName snake_case name, e.g. "articles_get_article"
|
|
14
|
-
* @param
|
|
15
|
+
* @param pathParams route path parameters used for cache key scoping
|
|
15
16
|
* @param extractorLines axum extractors needed by the operation
|
|
16
17
|
* @param serverArgsStr comma-separated args forwarded to the trait method
|
|
17
18
|
* @param responseName PascalCase name of the response enum, e.g. "ArticlesGetArticleResponse"
|
|
18
19
|
*/
|
|
19
20
|
export declare function generateEtagHandler(opts: {
|
|
20
21
|
handlerFnName: string;
|
|
21
|
-
|
|
22
|
+
pathParams: ParameterInfo[];
|
|
22
23
|
extractorLines: string[];
|
|
23
24
|
serverArgsStr: string;
|
|
24
25
|
responseName: string;
|
|
@@ -7,37 +7,72 @@
|
|
|
7
7
|
export function generateEtagCacheTrait() {
|
|
8
8
|
return `/// Pluggable ETag cache backend.
|
|
9
9
|
/// Implement this for Redis, Memcached, in-memory HashMap, or any store.
|
|
10
|
+
#[async_trait]
|
|
10
11
|
pub trait EtagCache {
|
|
11
12
|
/// Return the stored ETag string for \`key\`, or \`None\` if not cached.
|
|
12
|
-
fn get(&self, key: &str) -> Option<String>;
|
|
13
|
+
async fn get(&self, key: &str) -> Option<String>;
|
|
13
14
|
/// Store \`etag\` under \`key\`.
|
|
14
|
-
fn set(&self, key: &str, etag: &str);
|
|
15
|
+
async fn set(&self, key: &str, etag: &str);
|
|
15
16
|
}
|
|
16
17
|
`;
|
|
17
18
|
}
|
|
19
|
+
function rustStringLiteral(value) {
|
|
20
|
+
return `"${value
|
|
21
|
+
.replace(/\\/g, "\\\\")
|
|
22
|
+
.replace(/"/g, '\\"')
|
|
23
|
+
.replace(/\n/g, "\\n")
|
|
24
|
+
.replace(/\r/g, "\\r")
|
|
25
|
+
.replace(/\t/g, "\\t")}"`;
|
|
26
|
+
}
|
|
27
|
+
function buildCacheKeyExpression(opts) {
|
|
28
|
+
const { handlerFnName, etagKey, pathParams } = opts;
|
|
29
|
+
const pathSuffix = pathParams.map((p) => `{${p.rustName}}`).join(":");
|
|
30
|
+
const paramNames = new Map();
|
|
31
|
+
for (const param of pathParams) {
|
|
32
|
+
paramNames.set(param.name, param.rustName);
|
|
33
|
+
paramNames.set(param.rustName, param.rustName);
|
|
34
|
+
}
|
|
35
|
+
let template = etagKey ?? handlerFnName;
|
|
36
|
+
template = template.replace(/\{([^}]+)\}/g, (match, name) => {
|
|
37
|
+
const rustName = paramNames.get(name);
|
|
38
|
+
return rustName ? `{${rustName}}` : match;
|
|
39
|
+
});
|
|
40
|
+
const hasExplicitPathParam = pathParams.some((param) => template.includes(`{${param.name}}`) ||
|
|
41
|
+
template.includes(`{${param.rustName}}`));
|
|
42
|
+
if (pathSuffix && !hasExplicitPathParam) {
|
|
43
|
+
template = `${template}:${pathSuffix}`;
|
|
44
|
+
}
|
|
45
|
+
if (!template.includes("{")) {
|
|
46
|
+
return rustStringLiteral(template);
|
|
47
|
+
}
|
|
48
|
+
return `format!(${rustStringLiteral(template)})`;
|
|
49
|
+
}
|
|
18
50
|
/**
|
|
19
51
|
* generateEtagHandler
|
|
20
52
|
*
|
|
21
53
|
* Returns the Rust source for a single ETag-aware axum handler.
|
|
22
54
|
*
|
|
23
55
|
* @param handlerFnName snake_case name, e.g. "articles_get_article"
|
|
24
|
-
* @param
|
|
56
|
+
* @param pathParams route path parameters used for cache key scoping
|
|
25
57
|
* @param extractorLines axum extractors needed by the operation
|
|
26
58
|
* @param serverArgsStr comma-separated args forwarded to the trait method
|
|
27
59
|
* @param responseName PascalCase name of the response enum, e.g. "ArticlesGetArticleResponse"
|
|
28
60
|
*/
|
|
29
61
|
export function generateEtagHandler(opts) {
|
|
30
|
-
const { handlerFnName,
|
|
31
|
-
const effectiveCacheKey =
|
|
62
|
+
const { handlerFnName, pathParams, extractorLines, serverArgsStr, etagKey, cacheControl, serviceBinding = "service", } = opts;
|
|
63
|
+
const effectiveCacheKey = buildCacheKeyExpression({
|
|
64
|
+
handlerFnName,
|
|
65
|
+
etagKey,
|
|
66
|
+
pathParams,
|
|
67
|
+
});
|
|
32
68
|
const operationExtractors = extractorLines.length > 0 ? `${extractorLines.join("\n")}\n` : "";
|
|
33
69
|
const finalResponseLogic = ` match result {
|
|
34
70
|
Ok(response) => {
|
|
35
71
|
let mut res = response.into_response();
|
|
36
|
-
if let Some(stored_etag) = cache.get(cache_key.as_ref())
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
72
|
+
if let Some(stored_etag) = cache.get(cache_key.as_ref()).await
|
|
73
|
+
&& let Ok(etag) = axum::http::HeaderValue::from_str(&stored_etag)
|
|
74
|
+
{
|
|
75
|
+
res.headers_mut().insert(axum::http::header::ETAG, etag);
|
|
41
76
|
}
|
|
42
77
|
${cacheControl ? `res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("${cacheControl}"));` : ""}
|
|
43
78
|
res
|
|
@@ -60,13 +95,14 @@ where
|
|
|
60
95
|
{
|
|
61
96
|
// Check If-None-Match against the cache
|
|
62
97
|
let cache_key = ${effectiveCacheKey};
|
|
63
|
-
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
98
|
+
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
99
|
+
(cache.get(cache_key.as_ref()).await, if_none_match)
|
|
100
|
+
&& let Ok(etag) = stored_etag.parse::<axum_extra::headers::ETag>()
|
|
101
|
+
&& !inm.precondition_passes(&etag)
|
|
102
|
+
{
|
|
103
|
+
let mut res = axum::http::StatusCode::NOT_MODIFIED.into_response();
|
|
104
|
+
${cacheControl ? `res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("${cacheControl}"));` : ""}
|
|
105
|
+
return res;
|
|
70
106
|
}
|
|
71
107
|
// Forward to business logic
|
|
72
108
|
let result = ${serviceBinding}.${handlerFnName}(${serverArgsStr}).await;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"etag_router.js","sourceRoot":"","sources":["../../../src/generator/etag_router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"etag_router.js","sourceRoot":"","sources":["../../../src/generator/etag_router.ts"],"names":[],"mappings":"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"}
|
|
@@ -76,13 +76,12 @@ export function generateRouter(program, namespaceGroups, anonymousEnums) {
|
|
|
76
76
|
}
|
|
77
77
|
const serverArgsStr = serverArgs.join(", ");
|
|
78
78
|
if (hasEtagCache(program, op)) {
|
|
79
|
-
const cacheKey = `${handlerFnName}:${pathParams.map((p) => `{${p.rustName}}`).join(":")}`;
|
|
80
79
|
const cachedServerArgs = isProtected
|
|
81
80
|
? [serverArgs[0], "&cache", ...serverArgs.slice(1)]
|
|
82
81
|
: ["&cache", ...serverArgs];
|
|
83
82
|
const code = generateEtagHandler({
|
|
84
83
|
handlerFnName,
|
|
85
|
-
|
|
84
|
+
pathParams,
|
|
86
85
|
extractorLines,
|
|
87
86
|
serverArgsStr: cachedServerArgs.join(", "),
|
|
88
87
|
responseName: `${nsName}${toPascalCase(opInfo.name)}Response`,
|
|
@@ -102,7 +101,7 @@ export function generateRouter(program, namespaceGroups, anonymousEnums) {
|
|
|
102
101
|
continue;
|
|
103
102
|
}
|
|
104
103
|
const serverCall = `service.${traitFnName}(${serverArgsStr}).await`;
|
|
105
|
-
|
|
104
|
+
const responseHandling = opInfo.cacheControl
|
|
106
105
|
? `match result {
|
|
107
106
|
Ok(response) => {
|
|
108
107
|
let mut res = response.into_response();
|
|
@@ -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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server_trait.js","sourceRoot":"","sources":["../../../src/generator/server_trait.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,YAAY,GACb,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,MAAM,UAAU,mBAAmB,CACjC,OAAgB,EAChB,eAAoE,EACpE,cAAwD;IAExD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC;;;;;;;;;CASZ,CAAC,CAAC;IAED,
|
|
1
|
+
{"version":3,"file":"server_trait.js","sourceRoot":"","sources":["../../../src/generator/server_trait.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../../src/parser/operations.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../../src/parser/operations.ts"],"names":[],"mappings":"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"}
|
|
@@ -25,14 +25,17 @@ describe("@etagCache decorator", () => {
|
|
|
25
25
|
const results = await emit(ETAG_SPEC);
|
|
26
26
|
const server = results["server.rs"];
|
|
27
27
|
strictEqual(server.includes("pub trait EtagCache"), true);
|
|
28
|
-
strictEqual(server.includes("fn get(&self, key: &str) -> Option<String>"), true);
|
|
29
|
-
strictEqual(server.includes("fn set(&self, key: &str, etag: &str)"), true);
|
|
28
|
+
strictEqual(server.includes("async fn get(&self, key: &str) -> Option<String>"), true);
|
|
29
|
+
strictEqual(server.includes("async fn set(&self, key: &str, etag: &str)"), true);
|
|
30
30
|
});
|
|
31
31
|
it("generates cache-aware handler with 304 short-circuit", async () => {
|
|
32
32
|
const results = await emit(ETAG_SPEC);
|
|
33
33
|
const server = results["server.rs"];
|
|
34
34
|
strictEqual(server.includes("axum::http::StatusCode::NOT_MODIFIED"), true);
|
|
35
|
-
strictEqual(server.includes("cache.get(cache_key.as_ref())"), true);
|
|
35
|
+
strictEqual(server.includes("cache.get(cache_key.as_ref()).await"), true);
|
|
36
|
+
strictEqual(server.includes("stored_etag.parse::<axum_extra::headers::ETag>()"), true);
|
|
37
|
+
strictEqual(server.includes("!inm.precondition_passes(&etag)"), true);
|
|
38
|
+
strictEqual(server.includes("HeaderValue::from_str(&stored_etag).unwrap()"), false);
|
|
36
39
|
});
|
|
37
40
|
it("supports custom etagKey and @cacheControl", async () => {
|
|
38
41
|
const spec = `
|
|
@@ -51,9 +54,43 @@ describe("@etagCache decorator", () => {
|
|
|
51
54
|
const results = await emit(spec);
|
|
52
55
|
const server = results["server.rs"];
|
|
53
56
|
strictEqual(server.includes('let cache_key = "my-key";'), true);
|
|
54
|
-
strictEqual(server.includes(
|
|
57
|
+
strictEqual(server.includes("axum::http::header::CACHE_CONTROL"), true);
|
|
55
58
|
strictEqual(server.includes('"public"'), true);
|
|
56
59
|
});
|
|
60
|
+
it("interpolates path parameters in custom etag keys", async () => {
|
|
61
|
+
const spec = `
|
|
62
|
+
import "@typespec/http";
|
|
63
|
+
import "typespec-rust-emitter";
|
|
64
|
+
using TypeSpec.Http;
|
|
65
|
+
|
|
66
|
+
@route("/accounts/{accountId}/images")
|
|
67
|
+
namespace Images {
|
|
68
|
+
@etagCache("img:list:pub:{accountId}")
|
|
69
|
+
@get
|
|
70
|
+
op listPublic(@path accountId: string): { @statusCode statusCode: 200; };
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
const results = await emit(spec);
|
|
74
|
+
const server = results["server.rs"];
|
|
75
|
+
strictEqual(server.includes('let cache_key = format!("img:list:pub:{account_id}");'), true);
|
|
76
|
+
});
|
|
77
|
+
it("appends path parameters to static etag keys", async () => {
|
|
78
|
+
const spec = `
|
|
79
|
+
import "@typespec/http";
|
|
80
|
+
import "typespec-rust-emitter";
|
|
81
|
+
using TypeSpec.Http;
|
|
82
|
+
|
|
83
|
+
@route("/accounts/{accountId}/images")
|
|
84
|
+
namespace Images {
|
|
85
|
+
@etagCache("img:list:pub")
|
|
86
|
+
@get
|
|
87
|
+
op listPublic(@path accountId: string): { @statusCode statusCode: 200; };
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
const results = await emit(spec);
|
|
91
|
+
const server = results["server.rs"];
|
|
92
|
+
strictEqual(server.includes('let cache_key = format!("img:list:pub:{account_id}");'), true);
|
|
93
|
+
});
|
|
57
94
|
it("orders cache after claims when combined with @useAuth", async () => {
|
|
58
95
|
const spec = `
|
|
59
96
|
import "@typespec/http";
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"etag_cache.test.js","sourceRoot":"","sources":["../../test/etag_cache.test.ts"],"names":[],"mappings":"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"}
|
package/example/main.tsp
CHANGED
|
@@ -74,6 +74,30 @@ namespace Items {
|
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
@route("/accounts/{accountId}/images")
|
|
78
|
+
namespace Images {
|
|
79
|
+
@etagCache("img:list:pub:{accountId}")
|
|
80
|
+
@cacheControl("no-cache, max-age=2592000")
|
|
81
|
+
@get
|
|
82
|
+
op listPublic(@path accountId: Uuid): {
|
|
83
|
+
@statusCode statusCode: 200;
|
|
84
|
+
@body images: Item[];
|
|
85
|
+
} | {
|
|
86
|
+
@statusCode statusCode: 304;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
@etagCache("img:detail:pub")
|
|
90
|
+
@cacheControl("public, max-age=60")
|
|
91
|
+
@get
|
|
92
|
+
@route("/{imageId}")
|
|
93
|
+
op getImage(@path accountId: Uuid, @path imageId: string): {
|
|
94
|
+
@statusCode statusCode: 200;
|
|
95
|
+
@body image: Item;
|
|
96
|
+
} | {
|
|
97
|
+
@statusCode statusCode: 304;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
77
101
|
model Article {
|
|
78
102
|
id: string;
|
|
79
103
|
title: string;
|
|
@@ -12,11 +12,12 @@ use eyre::Result;
|
|
|
12
12
|
|
|
13
13
|
/// Pluggable ETag cache backend.
|
|
14
14
|
/// Implement this for Redis, Memcached, in-memory HashMap, or any store.
|
|
15
|
+
#[async_trait]
|
|
15
16
|
pub trait EtagCache {
|
|
16
17
|
/// Return the stored ETag string for `key`, or `None` if not cached.
|
|
17
|
-
fn get(&self, key: &str) -> Option<String>;
|
|
18
|
+
async fn get(&self, key: &str) -> Option<String>;
|
|
18
19
|
/// Store `etag` under `key`.
|
|
19
|
-
fn set(&self, key: &str, etag: &str);
|
|
20
|
+
async fn set(&self, key: &str, etag: &str);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
|
|
@@ -34,6 +35,12 @@ pub trait Server: Send + Sync {
|
|
|
34
35
|
async fn items_get_item(&self, id: String) -> Result<ItemsGetItemResponse>;
|
|
35
36
|
async fn items_create_item(&mut self, body: Item) -> Result<ItemsCreateItemResponse>;
|
|
36
37
|
async fn items_update_item(&mut self, id: String, body: Item) -> Result<ItemsUpdateItemResponse>;
|
|
38
|
+
async fn images_list_public<C: EtagCache + Send + Sync>(
|
|
39
|
+
&self, cache: &C, account_id: uuid::Uuid
|
|
40
|
+
) -> Result<ImagesListPublicResponse>;
|
|
41
|
+
async fn images_get_image<C: EtagCache + Send + Sync>(
|
|
42
|
+
&self, cache: &C, account_id: uuid::Uuid, image_id: String
|
|
43
|
+
) -> Result<ImagesGetImageResponse>;
|
|
37
44
|
async fn articles_get_article<C: EtagCache + Send + Sync>(
|
|
38
45
|
&self, claims: Self::Claims, cache: &C, id: String
|
|
39
46
|
) -> Result<ArticlesGetArticleResponse>;
|
|
@@ -124,6 +131,38 @@ impl IntoResponse for ItemsUpdateItemResponse {
|
|
|
124
131
|
}
|
|
125
132
|
}
|
|
126
133
|
|
|
134
|
+
#[allow(clippy::type_complexity)]
|
|
135
|
+
pub enum ImagesListPublicResponse {
|
|
136
|
+
Ok(Json<Vec<Item>>),
|
|
137
|
+
NotModified,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
impl IntoResponse for ImagesListPublicResponse {
|
|
141
|
+
fn into_response(self) -> axum::response::Response {
|
|
142
|
+
match self {
|
|
143
|
+
|
|
144
|
+
ImagesListPublicResponse::Ok(body) => (StatusCode::OK, body).into_response(),
|
|
145
|
+
ImagesListPublicResponse::NotModified => StatusCode::NOT_MODIFIED.into_response(),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#[allow(clippy::type_complexity)]
|
|
151
|
+
pub enum ImagesGetImageResponse {
|
|
152
|
+
Ok(Json<Item>),
|
|
153
|
+
NotModified,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
impl IntoResponse for ImagesGetImageResponse {
|
|
157
|
+
fn into_response(self) -> axum::response::Response {
|
|
158
|
+
match self {
|
|
159
|
+
|
|
160
|
+
ImagesGetImageResponse::Ok(body) => (StatusCode::OK, body).into_response(),
|
|
161
|
+
ImagesGetImageResponse::NotModified => StatusCode::NOT_MODIFIED.into_response(),
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
127
166
|
#[allow(clippy::type_complexity)]
|
|
128
167
|
pub enum ArticlesGetArticleResponse {
|
|
129
168
|
Ok(Json<Article>),
|
|
@@ -249,24 +288,24 @@ where
|
|
|
249
288
|
{
|
|
250
289
|
// Check If-None-Match against the cache
|
|
251
290
|
let cache_key = "article-list";
|
|
252
|
-
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
291
|
+
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
292
|
+
(cache.get(cache_key.as_ref()).await, if_none_match)
|
|
293
|
+
&& let Ok(etag) = stored_etag.parse::<axum_extra::headers::ETag>()
|
|
294
|
+
&& !inm.precondition_passes(&etag)
|
|
295
|
+
{
|
|
296
|
+
let mut res = axum::http::StatusCode::NOT_MODIFIED.into_response();
|
|
297
|
+
res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("public, max-age=3600"));
|
|
298
|
+
return res;
|
|
259
299
|
}
|
|
260
300
|
// Forward to business logic
|
|
261
301
|
let result = service.items_list(&cache).await;
|
|
262
302
|
match result {
|
|
263
303
|
Ok(response) => {
|
|
264
304
|
let mut res = response.into_response();
|
|
265
|
-
if let Some(stored_etag) = cache.get(cache_key.as_ref())
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
);
|
|
305
|
+
if let Some(stored_etag) = cache.get(cache_key.as_ref()).await
|
|
306
|
+
&& let Ok(etag) = axum::http::HeaderValue::from_str(&stored_etag)
|
|
307
|
+
{
|
|
308
|
+
res.headers_mut().insert(axum::http::header::ETAG, etag);
|
|
270
309
|
}
|
|
271
310
|
res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("public, max-age=3600"));
|
|
272
311
|
res
|
|
@@ -344,6 +383,92 @@ where
|
|
|
344
383
|
}
|
|
345
384
|
}
|
|
346
385
|
|
|
386
|
+
pub async fn images_list_public_handler<S, C>(
|
|
387
|
+
axum::extract::State(service): axum::extract::State<S>,
|
|
388
|
+
axum::extract::State(cache): axum::extract::State<C>,
|
|
389
|
+
if_none_match: Option<axum_extra::TypedHeader<axum_extra::headers::IfNoneMatch>>,
|
|
390
|
+
Path(account_id): Path<uuid::Uuid>,
|
|
391
|
+
) -> impl axum::response::IntoResponse
|
|
392
|
+
where
|
|
393
|
+
S: Server + Send + Sync + 'static,
|
|
394
|
+
S::Claims: Send + Sync + Clone + 'static,
|
|
395
|
+
C: EtagCache + Clone + Send + Sync + 'static,
|
|
396
|
+
{
|
|
397
|
+
// Check If-None-Match against the cache
|
|
398
|
+
let cache_key = format!("img:list:pub:{account_id}");
|
|
399
|
+
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
400
|
+
(cache.get(cache_key.as_ref()).await, if_none_match)
|
|
401
|
+
&& let Ok(etag) = stored_etag.parse::<axum_extra::headers::ETag>()
|
|
402
|
+
&& !inm.precondition_passes(&etag)
|
|
403
|
+
{
|
|
404
|
+
let mut res = axum::http::StatusCode::NOT_MODIFIED.into_response();
|
|
405
|
+
res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("no-cache, max-age=2592000"));
|
|
406
|
+
return res;
|
|
407
|
+
}
|
|
408
|
+
// Forward to business logic
|
|
409
|
+
let result = service.images_list_public(&cache, account_id).await;
|
|
410
|
+
match result {
|
|
411
|
+
Ok(response) => {
|
|
412
|
+
let mut res = response.into_response();
|
|
413
|
+
if let Some(stored_etag) = cache.get(cache_key.as_ref()).await
|
|
414
|
+
&& let Ok(etag) = axum::http::HeaderValue::from_str(&stored_etag)
|
|
415
|
+
{
|
|
416
|
+
res.headers_mut().insert(axum::http::header::ETAG, etag);
|
|
417
|
+
}
|
|
418
|
+
res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("no-cache, max-age=2592000"));
|
|
419
|
+
res
|
|
420
|
+
}
|
|
421
|
+
Err(e) => (
|
|
422
|
+
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
|
423
|
+
format!("Internal error: {e}"),
|
|
424
|
+
)
|
|
425
|
+
.into_response(),
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
pub async fn images_get_image_handler<S, C>(
|
|
430
|
+
axum::extract::State(service): axum::extract::State<S>,
|
|
431
|
+
axum::extract::State(cache): axum::extract::State<C>,
|
|
432
|
+
if_none_match: Option<axum_extra::TypedHeader<axum_extra::headers::IfNoneMatch>>,
|
|
433
|
+
Path((account_id, image_id)): Path<(uuid::Uuid, String)>,
|
|
434
|
+
) -> impl axum::response::IntoResponse
|
|
435
|
+
where
|
|
436
|
+
S: Server + Send + Sync + 'static,
|
|
437
|
+
S::Claims: Send + Sync + Clone + 'static,
|
|
438
|
+
C: EtagCache + Clone + Send + Sync + 'static,
|
|
439
|
+
{
|
|
440
|
+
// Check If-None-Match against the cache
|
|
441
|
+
let cache_key = format!("img:detail:pub:{account_id}:{image_id}");
|
|
442
|
+
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
443
|
+
(cache.get(cache_key.as_ref()).await, if_none_match)
|
|
444
|
+
&& let Ok(etag) = stored_etag.parse::<axum_extra::headers::ETag>()
|
|
445
|
+
&& !inm.precondition_passes(&etag)
|
|
446
|
+
{
|
|
447
|
+
let mut res = axum::http::StatusCode::NOT_MODIFIED.into_response();
|
|
448
|
+
res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("public, max-age=60"));
|
|
449
|
+
return res;
|
|
450
|
+
}
|
|
451
|
+
// Forward to business logic
|
|
452
|
+
let result = service.images_get_image(&cache, account_id, image_id).await;
|
|
453
|
+
match result {
|
|
454
|
+
Ok(response) => {
|
|
455
|
+
let mut res = response.into_response();
|
|
456
|
+
if let Some(stored_etag) = cache.get(cache_key.as_ref()).await
|
|
457
|
+
&& let Ok(etag) = axum::http::HeaderValue::from_str(&stored_etag)
|
|
458
|
+
{
|
|
459
|
+
res.headers_mut().insert(axum::http::header::ETAG, etag);
|
|
460
|
+
}
|
|
461
|
+
res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("public, max-age=60"));
|
|
462
|
+
res
|
|
463
|
+
}
|
|
464
|
+
Err(e) => (
|
|
465
|
+
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
|
466
|
+
format!("Internal error: {e}"),
|
|
467
|
+
)
|
|
468
|
+
.into_response(),
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
347
472
|
pub async fn articles_get_article_handler<S, C>(
|
|
348
473
|
axum::extract::State(service): axum::extract::State<S>,
|
|
349
474
|
axum::extract::State(cache): axum::extract::State<C>,
|
|
@@ -357,25 +482,25 @@ where
|
|
|
357
482
|
C: EtagCache + Clone + Send + Sync + 'static,
|
|
358
483
|
{
|
|
359
484
|
// Check If-None-Match against the cache
|
|
360
|
-
let cache_key = "article-list";
|
|
361
|
-
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
485
|
+
let cache_key = format!("article-list:{id}");
|
|
486
|
+
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
487
|
+
(cache.get(cache_key.as_ref()).await, if_none_match)
|
|
488
|
+
&& let Ok(etag) = stored_etag.parse::<axum_extra::headers::ETag>()
|
|
489
|
+
&& !inm.precondition_passes(&etag)
|
|
490
|
+
{
|
|
491
|
+
let mut res = axum::http::StatusCode::NOT_MODIFIED.into_response();
|
|
492
|
+
res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("public, max-age=3600"));
|
|
493
|
+
return res;
|
|
368
494
|
}
|
|
369
495
|
// Forward to business logic
|
|
370
496
|
let result = service.articles_get_article(claims, &cache, id).await;
|
|
371
497
|
match result {
|
|
372
498
|
Ok(response) => {
|
|
373
499
|
let mut res = response.into_response();
|
|
374
|
-
if let Some(stored_etag) = cache.get(cache_key.as_ref())
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
);
|
|
500
|
+
if let Some(stored_etag) = cache.get(cache_key.as_ref()).await
|
|
501
|
+
&& let Ok(etag) = axum::http::HeaderValue::from_str(&stored_etag)
|
|
502
|
+
{
|
|
503
|
+
res.headers_mut().insert(axum::http::header::ETAG, etag);
|
|
379
504
|
}
|
|
380
505
|
res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("public, max-age=3600"));
|
|
381
506
|
res
|
|
@@ -444,6 +569,8 @@ where
|
|
|
444
569
|
.route("/items/{id}", get(items_get_item_handler::<S>))
|
|
445
570
|
.route("/items", post(items_create_item_handler::<S>))
|
|
446
571
|
.route("/items", put(items_update_item_handler::<S>))
|
|
572
|
+
.route("/accounts/{accountId}/images", get(images_list_public_handler::<S, C>))
|
|
573
|
+
.route("/accounts/{accountId}/images/{imageId}", get(images_get_image_handler::<S, C>))
|
|
447
574
|
.route("/consuming", post(consuming_upload_handler::<S>))
|
|
448
575
|
;
|
|
449
576
|
router = router.merge(public);
|
|
@@ -7,8 +7,9 @@ use axum::response::IntoResponse;
|
|
|
7
7
|
use axum::response::sse::{Event, KeepAlive, Sse};
|
|
8
8
|
use generated::server::{
|
|
9
9
|
ArticlesGetArticleResponse, ConsumingConsumeAndDeleteResponse, ConsumingUploadResponse,
|
|
10
|
-
EtagCache, EventsAccountsEventsResponse,
|
|
11
|
-
|
|
10
|
+
EtagCache, EventsAccountsEventsResponse, ImagesGetImageResponse, ImagesListPublicResponse,
|
|
11
|
+
ItemsCreateItemResponse, ItemsGetItemResponse, ItemsListResponse, ItemsUpdateItemResponse,
|
|
12
|
+
PetsListResponse, Server,
|
|
12
13
|
};
|
|
13
14
|
use std::collections::HashMap;
|
|
14
15
|
use std::convert::Infallible;
|
|
@@ -20,11 +21,12 @@ use tokio_stream::{wrappers::IntervalStream, StreamExt as _};
|
|
|
20
21
|
#[derive(Clone, Default)]
|
|
21
22
|
struct InMemoryCache(Arc<Mutex<HashMap<String, String>>>);
|
|
22
23
|
|
|
24
|
+
#[async_trait]
|
|
23
25
|
impl EtagCache for InMemoryCache {
|
|
24
|
-
fn get(&self, key: &str) -> Option<String> {
|
|
26
|
+
async fn get(&self, key: &str) -> Option<String> {
|
|
25
27
|
self.0.lock().unwrap().get(key).cloned()
|
|
26
28
|
}
|
|
27
|
-
fn set(&self, key: &str, etag: &str) {
|
|
29
|
+
async fn set(&self, key: &str, etag: &str) {
|
|
28
30
|
self.0.lock().unwrap().insert(key.to_owned(), etag.to_owned());
|
|
29
31
|
}
|
|
30
32
|
}
|
|
@@ -71,7 +73,7 @@ impl Server for AppState {
|
|
|
71
73
|
&self,
|
|
72
74
|
cache: &C,
|
|
73
75
|
) -> eyre::Result<ItemsListResponse> {
|
|
74
|
-
cache.set("article-list", "\"items-etag\"");
|
|
76
|
+
cache.set("article-list", "\"items-etag\"").await;
|
|
75
77
|
Ok(ItemsListResponse::Ok(Json(vec![generated::types::Item {
|
|
76
78
|
name: "list-item".to_string(),
|
|
77
79
|
value: 123,
|
|
@@ -106,6 +108,47 @@ impl Server for AppState {
|
|
|
106
108
|
})))
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
async fn images_list_public<C: EtagCache + Send + Sync>(
|
|
112
|
+
&self,
|
|
113
|
+
cache: &C,
|
|
114
|
+
account_id: uuid::Uuid,
|
|
115
|
+
) -> eyre::Result<ImagesListPublicResponse> {
|
|
116
|
+
cache
|
|
117
|
+
.set(
|
|
118
|
+
&format!("img:list:pub:{}", account_id),
|
|
119
|
+
"\"images-list-etag\"",
|
|
120
|
+
)
|
|
121
|
+
.await;
|
|
122
|
+
Ok(ImagesListPublicResponse::Ok(Json(vec![
|
|
123
|
+
generated::types::Item {
|
|
124
|
+
name: "public-image-1".to_string(),
|
|
125
|
+
value: 200,
|
|
126
|
+
},
|
|
127
|
+
generated::types::Item {
|
|
128
|
+
name: "public-image-2".to_string(),
|
|
129
|
+
value: 201,
|
|
130
|
+
},
|
|
131
|
+
])))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async fn images_get_image<C: EtagCache + Send + Sync>(
|
|
135
|
+
&self,
|
|
136
|
+
cache: &C,
|
|
137
|
+
account_id: uuid::Uuid,
|
|
138
|
+
image_id: String,
|
|
139
|
+
) -> eyre::Result<ImagesGetImageResponse> {
|
|
140
|
+
cache
|
|
141
|
+
.set(
|
|
142
|
+
&format!("img:detail:pub:{}:{}", account_id, image_id),
|
|
143
|
+
"\"image-detail-etag\"",
|
|
144
|
+
)
|
|
145
|
+
.await;
|
|
146
|
+
Ok(ImagesGetImageResponse::Ok(Json(generated::types::Item {
|
|
147
|
+
name: format!("image-{}", image_id),
|
|
148
|
+
value: 300,
|
|
149
|
+
})))
|
|
150
|
+
}
|
|
151
|
+
|
|
109
152
|
async fn articles_get_article<C: EtagCache + Send + Sync>(
|
|
110
153
|
&self,
|
|
111
154
|
_claims: Self::Claims,
|
|
@@ -113,7 +156,7 @@ impl Server for AppState {
|
|
|
113
156
|
id: String,
|
|
114
157
|
) -> eyre::Result<ArticlesGetArticleResponse> {
|
|
115
158
|
let etag = format!("\"article-etag-{}\"", id);
|
|
116
|
-
cache.set(&format!("
|
|
159
|
+
cache.set(&format!("article-list:{}", id), &etag).await;
|
|
117
160
|
Ok(ArticlesGetArticleResponse::Ok(Json(
|
|
118
161
|
generated::types::Article {
|
|
119
162
|
id,
|
package/package.json
CHANGED
package/scripts/update-golden.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from
|
|
2
|
-
import { resolve } from
|
|
3
|
-
import { emit } from
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { emit } from "../dist/test/test-host.js";
|
|
4
4
|
|
|
5
5
|
async function main() {
|
|
6
6
|
const feature = process.argv[2];
|
|
7
7
|
if (!feature) {
|
|
8
|
-
console.error(
|
|
8
|
+
console.error("Usage: node scripts/update-golden.js <feature>");
|
|
9
9
|
process.exit(1);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const specPath = resolve(process.cwd(), `test/golden/${feature}/spec.tsp`);
|
|
13
|
-
const spec = readFileSync(specPath,
|
|
14
|
-
|
|
13
|
+
const spec = readFileSync(specPath, "utf8");
|
|
14
|
+
|
|
15
15
|
console.log(`Emitting spec for ${feature}...`);
|
|
16
16
|
const out = await emit(spec);
|
|
17
17
|
|
|
18
|
-
if (out[
|
|
18
|
+
if (out["types.rs"]) {
|
|
19
19
|
const p = resolve(process.cwd(), `test/golden/${feature}/types.rs`);
|
|
20
|
-
writeFileSync(p, out[
|
|
20
|
+
writeFileSync(p, out["types.rs"]);
|
|
21
21
|
console.log(`Updated types.rs`);
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
if (out[
|
|
23
|
+
|
|
24
|
+
if (out["server.rs"]) {
|
|
25
25
|
const p = resolve(process.cwd(), `test/golden/${feature}/server.rs`);
|
|
26
|
-
writeFileSync(p, out[
|
|
26
|
+
writeFileSync(p, out["server.rs"]);
|
|
27
27
|
console.log(`Updated server.rs`);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
console.log(`Golden files updated for: ${feature}`);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
main().catch(err => {
|
|
33
|
+
main().catch((err) => {
|
|
34
34
|
console.error(err);
|
|
35
35
|
process.exit(1);
|
|
36
36
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ParameterInfo } from "../models/types.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* generateEtagCacheTrait
|
|
3
5
|
*
|
|
@@ -7,29 +9,73 @@
|
|
|
7
9
|
export function generateEtagCacheTrait(): string {
|
|
8
10
|
return `/// Pluggable ETag cache backend.
|
|
9
11
|
/// Implement this for Redis, Memcached, in-memory HashMap, or any store.
|
|
12
|
+
#[async_trait]
|
|
10
13
|
pub trait EtagCache {
|
|
11
14
|
/// Return the stored ETag string for \`key\`, or \`None\` if not cached.
|
|
12
|
-
fn get(&self, key: &str) -> Option<String>;
|
|
15
|
+
async fn get(&self, key: &str) -> Option<String>;
|
|
13
16
|
/// Store \`etag\` under \`key\`.
|
|
14
|
-
fn set(&self, key: &str, etag: &str);
|
|
17
|
+
async fn set(&self, key: &str, etag: &str);
|
|
15
18
|
}
|
|
16
19
|
`;
|
|
17
20
|
}
|
|
18
21
|
|
|
22
|
+
function rustStringLiteral(value: string): string {
|
|
23
|
+
return `"${value
|
|
24
|
+
.replace(/\\/g, "\\\\")
|
|
25
|
+
.replace(/"/g, '\\"')
|
|
26
|
+
.replace(/\n/g, "\\n")
|
|
27
|
+
.replace(/\r/g, "\\r")
|
|
28
|
+
.replace(/\t/g, "\\t")}"`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildCacheKeyExpression(opts: {
|
|
32
|
+
handlerFnName: string;
|
|
33
|
+
etagKey?: string;
|
|
34
|
+
pathParams: ParameterInfo[];
|
|
35
|
+
}): string {
|
|
36
|
+
const { handlerFnName, etagKey, pathParams } = opts;
|
|
37
|
+
const pathSuffix = pathParams.map((p) => `{${p.rustName}}`).join(":");
|
|
38
|
+
const paramNames = new Map<string, string>();
|
|
39
|
+
for (const param of pathParams) {
|
|
40
|
+
paramNames.set(param.name, param.rustName);
|
|
41
|
+
paramNames.set(param.rustName, param.rustName);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let template = etagKey ?? handlerFnName;
|
|
45
|
+
template = template.replace(/\{([^}]+)\}/g, (match, name: string) => {
|
|
46
|
+
const rustName = paramNames.get(name);
|
|
47
|
+
return rustName ? `{${rustName}}` : match;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const hasExplicitPathParam = pathParams.some(
|
|
51
|
+
(param) =>
|
|
52
|
+
template.includes(`{${param.name}}`) ||
|
|
53
|
+
template.includes(`{${param.rustName}}`),
|
|
54
|
+
);
|
|
55
|
+
if (pathSuffix && !hasExplicitPathParam) {
|
|
56
|
+
template = `${template}:${pathSuffix}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!template.includes("{")) {
|
|
60
|
+
return rustStringLiteral(template);
|
|
61
|
+
}
|
|
62
|
+
return `format!(${rustStringLiteral(template)})`;
|
|
63
|
+
}
|
|
64
|
+
|
|
19
65
|
/**
|
|
20
66
|
* generateEtagHandler
|
|
21
67
|
*
|
|
22
68
|
* Returns the Rust source for a single ETag-aware axum handler.
|
|
23
69
|
*
|
|
24
70
|
* @param handlerFnName snake_case name, e.g. "articles_get_article"
|
|
25
|
-
* @param
|
|
71
|
+
* @param pathParams route path parameters used for cache key scoping
|
|
26
72
|
* @param extractorLines axum extractors needed by the operation
|
|
27
73
|
* @param serverArgsStr comma-separated args forwarded to the trait method
|
|
28
74
|
* @param responseName PascalCase name of the response enum, e.g. "ArticlesGetArticleResponse"
|
|
29
75
|
*/
|
|
30
76
|
export function generateEtagHandler(opts: {
|
|
31
77
|
handlerFnName: string;
|
|
32
|
-
|
|
78
|
+
pathParams: ParameterInfo[];
|
|
33
79
|
extractorLines: string[];
|
|
34
80
|
serverArgsStr: string;
|
|
35
81
|
responseName: string;
|
|
@@ -39,7 +85,7 @@ export function generateEtagHandler(opts: {
|
|
|
39
85
|
}): string {
|
|
40
86
|
const {
|
|
41
87
|
handlerFnName,
|
|
42
|
-
|
|
88
|
+
pathParams,
|
|
43
89
|
extractorLines,
|
|
44
90
|
serverArgsStr,
|
|
45
91
|
etagKey,
|
|
@@ -47,18 +93,21 @@ export function generateEtagHandler(opts: {
|
|
|
47
93
|
serviceBinding = "service",
|
|
48
94
|
} = opts;
|
|
49
95
|
|
|
50
|
-
const effectiveCacheKey =
|
|
96
|
+
const effectiveCacheKey = buildCacheKeyExpression({
|
|
97
|
+
handlerFnName,
|
|
98
|
+
etagKey,
|
|
99
|
+
pathParams,
|
|
100
|
+
});
|
|
51
101
|
const operationExtractors =
|
|
52
102
|
extractorLines.length > 0 ? `${extractorLines.join("\n")}\n` : "";
|
|
53
103
|
|
|
54
104
|
const finalResponseLogic = ` match result {
|
|
55
105
|
Ok(response) => {
|
|
56
106
|
let mut res = response.into_response();
|
|
57
|
-
if let Some(stored_etag) = cache.get(cache_key.as_ref())
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
107
|
+
if let Some(stored_etag) = cache.get(cache_key.as_ref()).await
|
|
108
|
+
&& let Ok(etag) = axum::http::HeaderValue::from_str(&stored_etag)
|
|
109
|
+
{
|
|
110
|
+
res.headers_mut().insert(axum::http::header::ETAG, etag);
|
|
62
111
|
}
|
|
63
112
|
${cacheControl ? `res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("${cacheControl}"));` : ""}
|
|
64
113
|
res
|
|
@@ -82,13 +131,14 @@ where
|
|
|
82
131
|
{
|
|
83
132
|
// Check If-None-Match against the cache
|
|
84
133
|
let cache_key = ${effectiveCacheKey};
|
|
85
|
-
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
134
|
+
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
135
|
+
(cache.get(cache_key.as_ref()).await, if_none_match)
|
|
136
|
+
&& let Ok(etag) = stored_etag.parse::<axum_extra::headers::ETag>()
|
|
137
|
+
&& !inm.precondition_passes(&etag)
|
|
138
|
+
{
|
|
139
|
+
let mut res = axum::http::StatusCode::NOT_MODIFIED.into_response();
|
|
140
|
+
${cacheControl ? `res.headers_mut().insert(axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("${cacheControl}"));` : ""}
|
|
141
|
+
return res;
|
|
92
142
|
}
|
|
93
143
|
// Forward to business logic
|
|
94
144
|
let result = ${serviceBinding}.${handlerFnName}(${serverArgsStr}).await;
|
package/src/generator/router.ts
CHANGED
|
@@ -117,13 +117,12 @@ export function generateRouter(
|
|
|
117
117
|
const serverArgsStr = serverArgs.join(", ");
|
|
118
118
|
|
|
119
119
|
if (hasEtagCache(program, op)) {
|
|
120
|
-
const cacheKey = `${handlerFnName}:${pathParams.map((p) => `{${p.rustName}}`).join(":")}`;
|
|
121
120
|
const cachedServerArgs = isProtected
|
|
122
121
|
? [serverArgs[0], "&cache", ...serverArgs.slice(1)]
|
|
123
122
|
: ["&cache", ...serverArgs];
|
|
124
123
|
const code = generateEtagHandler({
|
|
125
124
|
handlerFnName,
|
|
126
|
-
|
|
125
|
+
pathParams,
|
|
127
126
|
extractorLines,
|
|
128
127
|
serverArgsStr: cachedServerArgs.join(", "),
|
|
129
128
|
responseName: `${nsName}${toPascalCase(opInfo.name)}Response`,
|
|
@@ -144,7 +143,7 @@ export function generateRouter(
|
|
|
144
143
|
|
|
145
144
|
const serverCall = `service.${traitFnName}(${serverArgsStr}).await`;
|
|
146
145
|
|
|
147
|
-
|
|
146
|
+
const responseHandling = opInfo.cacheControl
|
|
148
147
|
? `match result {
|
|
149
148
|
Ok(response) => {
|
|
150
149
|
let mut res = response.into_response();
|
|
@@ -29,7 +29,11 @@ use eyre::Result;
|
|
|
29
29
|
|
|
30
30
|
`);
|
|
31
31
|
|
|
32
|
-
if (
|
|
32
|
+
if (
|
|
33
|
+
namespaceGroups.some((g) =>
|
|
34
|
+
g.operations.some((o) => hasEtagCache(program, o)),
|
|
35
|
+
)
|
|
36
|
+
) {
|
|
33
37
|
parts.push(generateEtagCacheTrait());
|
|
34
38
|
parts.push("\n");
|
|
35
39
|
}
|
package/src/parser/operations.ts
CHANGED
|
@@ -128,7 +128,9 @@ export function emitOperationInfo(
|
|
|
128
128
|
const opName = op.name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
129
129
|
const etagVal = program.stateMap(etagCacheKey).get(op);
|
|
130
130
|
const etagKey = typeof etagVal === "string" ? etagVal : undefined;
|
|
131
|
-
const cacheControl = program.stateMap(cacheControlKey).get(op) as
|
|
131
|
+
const cacheControl = program.stateMap(cacheControlKey).get(op) as
|
|
132
|
+
| string
|
|
133
|
+
| undefined;
|
|
132
134
|
|
|
133
135
|
return {
|
|
134
136
|
name: opName,
|
|
@@ -144,7 +146,10 @@ export function emitOperationInfo(
|
|
|
144
146
|
};
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
export function getEtagKey(
|
|
149
|
+
export function getEtagKey(
|
|
150
|
+
program: Program,
|
|
151
|
+
operation: Operation,
|
|
152
|
+
): string | undefined {
|
|
148
153
|
const val = program.stateMap(etagCacheKey).get(operation);
|
|
149
154
|
return typeof val === "string" ? val : undefined;
|
|
150
155
|
}
|
|
@@ -153,6 +158,9 @@ export function hasEtagCache(program: Program, operation: Operation): boolean {
|
|
|
153
158
|
return program.stateMap(etagCacheKey).has(operation);
|
|
154
159
|
}
|
|
155
160
|
|
|
156
|
-
export function getCacheControl(
|
|
161
|
+
export function getCacheControl(
|
|
162
|
+
program: Program,
|
|
163
|
+
operation: Operation,
|
|
164
|
+
): string | undefined {
|
|
157
165
|
return program.stateMap(cacheControlKey).get(operation) as string | undefined;
|
|
158
166
|
}
|
package/test/etag_cache.test.ts
CHANGED
|
@@ -28,17 +28,29 @@ describe("@etagCache decorator", () => {
|
|
|
28
28
|
const server = results["server.rs"];
|
|
29
29
|
strictEqual(server.includes("pub trait EtagCache"), true);
|
|
30
30
|
strictEqual(
|
|
31
|
-
server.includes("fn get(&self, key: &str) -> Option<String>"),
|
|
31
|
+
server.includes("async fn get(&self, key: &str) -> Option<String>"),
|
|
32
|
+
true,
|
|
33
|
+
);
|
|
34
|
+
strictEqual(
|
|
35
|
+
server.includes("async fn set(&self, key: &str, etag: &str)"),
|
|
32
36
|
true,
|
|
33
37
|
);
|
|
34
|
-
strictEqual(server.includes("fn set(&self, key: &str, etag: &str)"), true);
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
it("generates cache-aware handler with 304 short-circuit", async () => {
|
|
38
41
|
const results = await emit(ETAG_SPEC);
|
|
39
42
|
const server = results["server.rs"];
|
|
40
43
|
strictEqual(server.includes("axum::http::StatusCode::NOT_MODIFIED"), true);
|
|
41
|
-
strictEqual(server.includes("cache.get(cache_key.as_ref())"), true);
|
|
44
|
+
strictEqual(server.includes("cache.get(cache_key.as_ref()).await"), true);
|
|
45
|
+
strictEqual(
|
|
46
|
+
server.includes("stored_etag.parse::<axum_extra::headers::ETag>()"),
|
|
47
|
+
true,
|
|
48
|
+
);
|
|
49
|
+
strictEqual(server.includes("!inm.precondition_passes(&etag)"), true);
|
|
50
|
+
strictEqual(
|
|
51
|
+
server.includes("HeaderValue::from_str(&stored_etag).unwrap()"),
|
|
52
|
+
false,
|
|
53
|
+
);
|
|
42
54
|
});
|
|
43
55
|
|
|
44
56
|
it("supports custom etagKey and @cacheControl", async () => {
|
|
@@ -58,10 +70,52 @@ describe("@etagCache decorator", () => {
|
|
|
58
70
|
const results = await emit(spec);
|
|
59
71
|
const server = results["server.rs"];
|
|
60
72
|
strictEqual(server.includes('let cache_key = "my-key";'), true);
|
|
61
|
-
strictEqual(server.includes(
|
|
73
|
+
strictEqual(server.includes("axum::http::header::CACHE_CONTROL"), true);
|
|
62
74
|
strictEqual(server.includes('"public"'), true);
|
|
63
75
|
});
|
|
64
76
|
|
|
77
|
+
it("interpolates path parameters in custom etag keys", async () => {
|
|
78
|
+
const spec = `
|
|
79
|
+
import "@typespec/http";
|
|
80
|
+
import "typespec-rust-emitter";
|
|
81
|
+
using TypeSpec.Http;
|
|
82
|
+
|
|
83
|
+
@route("/accounts/{accountId}/images")
|
|
84
|
+
namespace Images {
|
|
85
|
+
@etagCache("img:list:pub:{accountId}")
|
|
86
|
+
@get
|
|
87
|
+
op listPublic(@path accountId: string): { @statusCode statusCode: 200; };
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
const results = await emit(spec);
|
|
91
|
+
const server = results["server.rs"];
|
|
92
|
+
strictEqual(
|
|
93
|
+
server.includes('let cache_key = format!("img:list:pub:{account_id}");'),
|
|
94
|
+
true,
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("appends path parameters to static etag keys", async () => {
|
|
99
|
+
const spec = `
|
|
100
|
+
import "@typespec/http";
|
|
101
|
+
import "typespec-rust-emitter";
|
|
102
|
+
using TypeSpec.Http;
|
|
103
|
+
|
|
104
|
+
@route("/accounts/{accountId}/images")
|
|
105
|
+
namespace Images {
|
|
106
|
+
@etagCache("img:list:pub")
|
|
107
|
+
@get
|
|
108
|
+
op listPublic(@path accountId: string): { @statusCode statusCode: 200; };
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
const results = await emit(spec);
|
|
112
|
+
const server = results["server.rs"];
|
|
113
|
+
strictEqual(
|
|
114
|
+
server.includes('let cache_key = format!("img:list:pub:{account_id}");'),
|
|
115
|
+
true,
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
65
119
|
it("orders cache after claims when combined with @useAuth", async () => {
|
|
66
120
|
const spec = `
|
|
67
121
|
import "@typespec/http";
|
|
@@ -12,11 +12,12 @@ use eyre::Result;
|
|
|
12
12
|
|
|
13
13
|
/// Pluggable ETag cache backend.
|
|
14
14
|
/// Implement this for Redis, Memcached, in-memory HashMap, or any store.
|
|
15
|
+
#[async_trait]
|
|
15
16
|
pub trait EtagCache {
|
|
16
17
|
/// Return the stored ETag string for `key`, or `None` if not cached.
|
|
17
|
-
fn get(&self, key: &str) -> Option<String>;
|
|
18
|
+
async fn get(&self, key: &str) -> Option<String>;
|
|
18
19
|
/// Store `etag` under `key`.
|
|
19
|
-
fn set(&self, key: &str, etag: &str);
|
|
20
|
+
async fn set(&self, key: &str, etag: &str);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
|
|
@@ -64,24 +65,24 @@ where
|
|
|
64
65
|
{
|
|
65
66
|
// Check If-None-Match against the cache
|
|
66
67
|
let cache_key = format!("articles_get_article:{id}");
|
|
67
|
-
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
if let (Some(stored_etag), Some(axum_extra::TypedHeader(inm))) =
|
|
69
|
+
(cache.get(cache_key.as_ref()).await, if_none_match)
|
|
70
|
+
&& let Ok(etag) = stored_etag.parse::<axum_extra::headers::ETag>()
|
|
71
|
+
&& !inm.precondition_passes(&etag)
|
|
72
|
+
{
|
|
73
|
+
let mut res = axum::http::StatusCode::NOT_MODIFIED.into_response();
|
|
74
|
+
|
|
75
|
+
return res;
|
|
74
76
|
}
|
|
75
77
|
// Forward to business logic
|
|
76
78
|
let result = service.articles_get_article(&cache, id).await;
|
|
77
79
|
match result {
|
|
78
80
|
Ok(response) => {
|
|
79
81
|
let mut res = response.into_response();
|
|
80
|
-
if let Some(stored_etag) = cache.get(cache_key.as_ref())
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
);
|
|
82
|
+
if let Some(stored_etag) = cache.get(cache_key.as_ref()).await
|
|
83
|
+
&& let Ok(etag) = axum::http::HeaderValue::from_str(&stored_etag)
|
|
84
|
+
{
|
|
85
|
+
res.headers_mut().insert(axum::http::header::ETAG, etag);
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
res
|