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 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 pathParams list of { rustName, rustType } for Path extractor
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
- pathParams: {
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 pathParams list of { rustName, rustType } for Path extractor
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, pathParams, serverArgsStr, etagKey, cacheControl, } = opts;
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
- let responseLogic = ` match result {
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(service): axum::extract::State<S>,
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
- ${pathExtractor}) -> impl axum::response::IntoResponse
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 = service.${handlerFnName}(&cache, ${serverArgsStr}).await;
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,IAQnC;IACC,MAAM,EACJ,aAAa,EACb,QAAQ,EACR,UAAU,EACV,aAAa,EACb,OAAO,EACP,YAAY,GACb,GAAG,IAAI,CAAC;IAET,uBAAuB;IACvB,IAAI,aAAa,GAAG,EAAE,CAAC;IACvB,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,aAAa,GAAG,YAAY,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,WAAW,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC;IAC5F,CAAC;SAAM,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,aAAa,GAAG,aAAa,KAAK,aAAa,KAAK,OAAO,CAAC;IAC9D,CAAC;IAED,MAAM,iBAAiB,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,YAAY,QAAQ,IAAI,CAAC;IAE9E,IAAI,aAAa,GAAG;;;CAGrB,CAAC;IAEA,MAAM,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,2EAA2E;IAC9G,2CAA2C;IAE3C,IAAI,YAAY,EAAE,CAAC;QACjB,aAAa,GAAG;;;;;wDAKoC,YAAY;;CAEnE,CAAC;IACA,CAAC;IAED,yEAAyE;IACzE,gHAAgH;IAChH,+DAA+D;IAE/D,aAAa,IAAI;;;;;;;;;;;;;;;;;CAiBlB,CAAC;IAEA,+EAA+E;IAC/E,wDAAwD;IAExD,MAAM,kBAAkB,GAAG;;;;;;;;;cASf,YAAY,CAAC,CAAC,CAAC,qGAAqG,YAAY,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;MAQnJ,CAAC;IAEL,OAAO,gBAAgB,aAAa;;;;EAIpC,aAAa;;;;;;sBAMO,iBAAiB;;;;;cAKzB,YAAY,CAAC,CAAC,CAAC,qGAAqG,YAAY,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;2BAK9H,aAAa,YAAY,aAAa;EAC/D,kBAAkB;EAClB,CAAC;AACH,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
- pathParams,
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":"AAEA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,YAAY,GACb,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,UAAU,cAAc,CAC5B,OAAgB,EAChB,eAAoE,EACpE,cAAwD;IAExD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,gBAAgB,GAAa,EAAE,CAAC;IACtC,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,eAAe,GAAa,EAAE,CAAC;IACrC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAEtC,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CACzC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CACnD,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QACvD,IAAI,CAAC,OAAO;YAAE,SAAS;QAEvB,MAAM,MAAM,GAAG,YAAY,CACzB,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CACpD,CAAC;QAEF,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;YACvE,IAAI,CAAC,MAAM;gBAAE,SAAS;YAEtB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YAC3C,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAExB,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAC9D,MAAM,WAAW,GAAG,aAAa,CAAC;YAClC,MAAM,WAAW,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;YACzC,MAAM,YAAY,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;YAEzC,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC;YAC1E,MAAM,aAAa,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;YAC5C,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAC1C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAC9B,CAAC;YACF,MAAM,cAAc,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;YAC9C,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;YAC9B,MAAM,eAAe,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;YAE7C,MAAM,cAAc,GAAa,EAAE,CAAC;YACpC,MAAM,UAAU,GAAa,EAAE,CAAC;YAEhC,MAAM,cAAc,GAClB,YAAY,KAAK,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;YAE3D,IAAI,WAAW,EAAE,CAAC;gBAChB,cAAc,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;gBACpE,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC5B,CAAC;YAED,IAAI,aAAa,EAAE,CAAC;gBAClB,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC/D,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAChE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC5B,cAAc,CAAC,IAAI,CAAC,YAAY,UAAU,WAAW,SAAS,IAAI,CAAC,CAAC;gBACtE,CAAC;qBAAM,CAAC;oBACN,cAAc,CAAC,IAAI,CACjB,aAAa,UAAU,aAAa,SAAS,KAAK,CACnD,CAAC;gBACJ,CAAC;gBACD,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;oBAC/B,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAClC,CAAC;YACH,CAAC;YAED,IAAI,cAAc,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7C,MAAM,aAAa,GAAG,GAAG,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC;gBAC5D,MAAM,WAAW,GAAG,WAAW;qBAC5B,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CACJ,yBAAyB,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ,EAAE,CAC7E;qBACA,IAAI,CAAC,KAAK,CAAC,CAAC;gBACf,gBAAgB,CAAC,IAAI,CACnB,2DAA2D,aAAa,OAAO,WAAW,KAAK,CAChG,CAAC;gBACF,cAAc,CAAC,IAAI,CAAC,4BAA4B,aAAa,IAAI,CAAC,CAAC;gBACnE,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;oBAChC,UAAU,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;YAED,IAAI,OAAO,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC3B,IAAI,eAAe,EAAE,CAAC;oBACpB,cAAc,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;oBAChE,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBAC/B,CAAC;qBAAM,CAAC;oBACN,MAAM,QAAQ,GAAG,sBAAsB,CACrC,MAAM,CAAC,IAAI,CAAC,IAAI,EAChB,OAAO,EACP,cAAc,CACf,CAAC;oBACF,cAAc,CAAC,IAAI,CAAC,2BAA2B,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC;oBAClE,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC7B,CAAC;YACH,CAAC;YAED,MAAM,aAAa,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAE5C,IAAI,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;gBAC9B,MAAM,QAAQ,GAAG,GAAG,aAAa,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC1F,MAAM,IAAI,GAAG,mBAAmB,CAAC;oBAC/B,aAAa;oBACb,QAAQ;oBACR,UAAU;oBACV,aAAa;oBACb,YAAY,EAAE,GAAG,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU;oBAC7D,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,YAAY,EAAE,MAAM,CAAC,YAAY;iBAClC,CAAC,CAAC;gBACH,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACpB,MAAM,SAAS,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC;gBACrC,MAAM,SAAS,GAAG,UAAU,SAAS,KAAK,MAAM,IAAI,aAAa,oBAAoB,CAAC;gBACtF,IAAI,WAAW,EAAE,CAAC;oBAChB,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAClC,CAAC;qBAAM,CAAC;oBACN,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC/B,CAAC;gBACD,SAAS;YACX,CAAC;YAED,MAAM,UAAU,GAAG,WAAW,WAAW,IAAI,aAAa,SAAS,CAAC;YAEpE,IAAI,gBAAgB,GAAG,MAAM,CAAC,YAAY;gBACxC,CAAC,CAAC;;;;;wDAK8C,MAAM,CAAC,YAAY;;;;;;;;;MASrE;gBACE,CAAC,CAAC;;;;;;;MAOJ,CAAC;YAED,MAAM,UAAU,GAAG,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,MAAM,WAAW,GACf,YAAY,KAAK,MAAM;gBACrB,CAAC,CAAC,YAAY,aAAa;;eAEtB,aAAa;;EAE1B,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;2BAMA,WAAW,IAAI,aAAa;MACjD,gBAAgB;EACpB;gBACQ,CAAC,CAAC,gBAAgB,aAAa;2BACd,cAAc;EACvC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;;;eAGZ,UAAU;;;mBAGN,UAAU;MACvB,gBAAgB;EACpB,CAAC;YAEG,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAE3B,IAAI,YAAY,KAAK,MAAM,EAAE,CAAC;gBAC5B,SAAS;YACX,CAAC;YAED,MAAM,SAAS,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC;YACrC,IAAI,SAAS,GAAG,EAAE,CAAC;YACnB,IAAI,WAAW,EAAE,CAAC;gBAChB,SAAS,GAAG,UAAU,SAAS,KAAK,MAAM,IAAI,aAAa,iBAAiB,CAAC;gBAC7E,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAClC,CAAC;iBAAM,CAAC;gBACN,SAAS,GAAG,UAAU,SAAS,KAAK,MAAM,IAAI,aAAa,iBAAiB,CAAC;gBAC7E,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEhE,MAAM,UAAU,GAAG,eAAe,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;IAElE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC;sBACS,aAAa;;;CAGlC,CAAC,CAAC;IACD,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrB,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAElC,IAAI,OAAO,EAAE,CAAC;QACZ,KAAK,CAAC,IAAI,CAAC;;;;;;;;EAQb,UAAU,CAAC,OAAO,CAAC,eAAe,EAAE,iCAAiC,CAAC;EACtE,CAAC,CAAC;IACF,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC;;;;;;;EAOb,UAAU;EACV,CAAC,CAAC;IACF,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,YAAsB,EACtB,eAAyB;IAEzB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;IAClD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QAC7C,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACpC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;QAChD,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;YAChC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACpC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;IAClE,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;IAC7C,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
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
- @cacheControl("no-cache")
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(&self) -> Result<ItemsListResponse>;
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+ Clone + Send + Sync + 'static,
246
+ S: Server + Send + Sync + 'static,
244
247
  S::Claims: Send + Sync + Clone + 'static,
248
+ C: EtagCache + Clone + Send + Sync + 'static,
245
249
  {
246
- let result = service.items_list().await;
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
- res.headers_mut().insert(
251
- axum::http::header::CACHE_CONTROL,
252
- axum::http::HeaderValue::from_static("no-cache"),
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) => response.into_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(&self) -> eyre::Result<ItemsListResponse> {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typespec-rust-emitter",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "description": "TypeSpec emitter that generates idiomatic Rust types and structs",
5
5
  "keywords": [
6
6
  "typespec",
@@ -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 pathParams list of { rustName, rustType } for Path extractor
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
- pathParams: { rustName: string; rustType: string }[];
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
- pathParams,
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
- let responseLogic = ` match result {
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(service): axum::extract::State<S>,
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
- ${pathExtractor}) -> impl axum::response::IntoResponse
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 = service.${handlerFnName}(&cache, ${serverArgsStr}).await;
94
+ let result = ${serviceBinding}.${handlerFnName}(${serverArgsStr}).await;
145
95
  ${finalResponseLogic}
146
96
  }`;
147
97
  }
@@ -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
- pathParams,
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}"`;
@@ -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