Item 259: an optional query string held in a const is the URL's query, not a path segment
This commit is contained in:
@@ -4,4 +4,4 @@
|
||||
server.url=http://localhost:8787
|
||||
# Stamped by manage-ac.sh (stamp_cli_version) from ac-code-server's agenticcode.version
|
||||
# at build time. "dev" means this jar wasn't built via manage-ac.sh.
|
||||
version=399
|
||||
version=400
|
||||
|
||||
@@ -3,7 +3,7 @@ quarkus.http.port=8787
|
||||
# AgenticCode's own release counter (not the Maven project version) — bump this by hand for each
|
||||
# release. Single source of truth for the startup log line, GET /api/version, and the OpenAPI
|
||||
# info version (referenced below via property expression, not duplicated).
|
||||
agenticcode.version=399
|
||||
agenticcode.version=400
|
||||
# OpenAPI / Swagger UI (item 48) — the generated spec is the contract the web-UI TS client
|
||||
# is generated against. Served at /q/openapi (yaml/json); Swagger UI at /q/swagger-ui in dev.
|
||||
mp.openapi.extensions.smallrye.info.title=AgenticCode API
|
||||
|
||||
@@ -140,6 +140,19 @@ class AngularApiIT {
|
||||
responses:
|
||||
200:
|
||||
description: ok
|
||||
/api/login/spnego:
|
||||
get:
|
||||
operationId: spnego
|
||||
tags:
|
||||
- Authentication
|
||||
parameters:
|
||||
- name: forcedTenant
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: ok
|
||||
/api/v1/orders:
|
||||
get:
|
||||
operationId: listOrders
|
||||
@@ -166,6 +179,10 @@ class AngularApiIT {
|
||||
public boolean login() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean spnego(String forcedTenant) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
""");
|
||||
// item 252: tag `i18n`, delegate `I18nApiDelegate`
|
||||
@@ -287,6 +304,8 @@ class AngularApiIT {
|
||||
// `POST /ShopApi/api/login`, served by the delegate method (item 253)
|
||||
.body("find { it.name == 'OrderApiService.login' }.counterpartModule", equalTo("shop.api.AuthenticationController"))
|
||||
.body("find { it.name == 'OrderApiService.login' }.counterpartName", equalTo("login"))
|
||||
// `'/ShopApi/api/login/spnego' + param`, param an optional `?forcedTenant=…` (item 259)
|
||||
.body("find { it.name == 'OrderApiService.spnego' }.counterpartName", equalTo("spnego"))
|
||||
// generated delegate: <Tag>ApiDelegate + operationId
|
||||
.body("find { it.name == 'OrdersService.listOrders' }.counterpartName", equalTo("listOrders"))
|
||||
.body("find { it.name == 'OrdersService.listOrders' }.counterpartModule", equalTo("shop.api.OrdersController"))
|
||||
|
||||
@@ -801,16 +801,18 @@ function typeText(expr) {
|
||||
}
|
||||
|
||||
// The URL as far as it folds: literal parts as written, the rest `{name}` (the last identifier of the expression).
|
||||
function urlText(sf, expr) {
|
||||
function urlText(sf, expr, depth = 0) {
|
||||
expr = unwrap(expr);
|
||||
const folded = literalString(expr);
|
||||
if (folded !== null) return folded;
|
||||
if (ts.isTemplateExpression(expr)) {
|
||||
return expr.head.text + expr.templateSpans.map((sp) => urlText(sf, sp.expression) + sp.literal.text).join('');
|
||||
return expr.head.text + expr.templateSpans.map((sp) => urlText(sf, sp.expression, depth) + sp.literal.text).join('');
|
||||
}
|
||||
if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
||||
return urlText(sf, expr.left) + urlText(sf, expr.right);
|
||||
return urlText(sf, expr.left, depth) + urlText(sf, expr.right, depth);
|
||||
}
|
||||
const query = optionalQuery(sf, expr, depth);
|
||||
if (query !== null) return query;
|
||||
let e = expr;
|
||||
while (ts.isCallExpression(e) && e.arguments.length) e = unwrap(e.arguments[0]); // encodeURIComponent(x)
|
||||
if (ts.isPropertyAccessExpression(e)) return `{${e.name.text}}`;
|
||||
@@ -818,6 +820,28 @@ function urlText(sf, expr) {
|
||||
return '{' + e.getText(sf).replace(/\s+/g, '') + '}';
|
||||
}
|
||||
|
||||
// Item 259: an optional query string — `const param = t ? '?forcedTenant=' + t : ''; get(api + param)` —
|
||||
// is the non-empty branch (`?forcedTenant={t}`), not a `{param}` path segment. Only a conditional with
|
||||
// one branch '' and the other starting with '?' or '&', inline or as the initializer of a const.
|
||||
function optionalQuery(sf, expr, depth) {
|
||||
if (depth > 5) return null;
|
||||
if (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr)) {
|
||||
const d = declarationOf(ts.isIdentifier(expr) ? expr : expr.name);
|
||||
const isConst = d && ts.isVariableDeclaration(d) && d.initializer && ts.isVariableDeclarationList(d.parent)
|
||||
&& (d.parent.flags & ts.NodeFlags.Const) !== 0;
|
||||
if (!isConst) return null;
|
||||
const init = unwrap(d.initializer);
|
||||
return ts.isConditionalExpression(init) ? optionalQuery(sf, init, depth + 1) : null;
|
||||
}
|
||||
if (!ts.isConditionalExpression(expr)) return null;
|
||||
for (const [empty, other] of [[expr.whenFalse, expr.whenTrue], [expr.whenTrue, expr.whenFalse]]) {
|
||||
if (literalString(empty) !== '') continue;
|
||||
const text = urlText(sf, other, depth + 1);
|
||||
if (text.startsWith('?') || text.startsWith('&')) return text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// hey-api (@hey-api/openapi-ts sdk.gen.ts): `export const getX = (options) => (options.client ?? client).get<...>({ url: '/x/{id}', ...options })`.
|
||||
function heyApiEndpoint(sf, name, fn, out) {
|
||||
let found = null;
|
||||
|
||||
@@ -173,7 +173,9 @@ class AngularSidecarTest {
|
||||
"OrderApiService.load|GET|/ShopData/api/v1/orders/{id}|httpclient|null|OrderSummary",
|
||||
"OrderApiService.store|POST|/ShopData/api/v1/orders|httpclient|OrderSummary|OrderSummary",
|
||||
"OrderApiService.twice|GET|/ShopData/api/v1/twice|httpclient|null|string",
|
||||
"OrderApiService.login|POST|/ShopApi/api/login|httpclient|{}|boolean"), http);
|
||||
"OrderApiService.login|POST|/ShopApi/api/login|httpclient|{}|boolean",
|
||||
// item 259: an optional query string held in a const is the query, not a path segment
|
||||
"OrderApiService.spnego|GET|/ShopApi/api/login/spnego?forcedTenant={tenant}|httpclient|null|boolean"), http);
|
||||
|
||||
assertEquals("spec/shop-api.yaml", facts.virtualFiles().get("app/src/generated/api/orders.service.ts"));
|
||||
assertEquals("spec/shop-api.yaml", facts.virtualFiles().get("app/src/generated/model/orderDTO.ts"));
|
||||
|
||||
@@ -27,4 +27,9 @@ export class OrderApiService {
|
||||
login(): Observable<boolean> {
|
||||
return this.http.post<boolean>('/ShopApi/api/login', {});
|
||||
}
|
||||
|
||||
spnego(tenant?: string): Observable<boolean> {
|
||||
const param = tenant ? '?forcedTenant=' + tenant : '';
|
||||
return this.http.get<boolean>('/ShopApi/api/login/spnego' + param);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1660,7 +1660,9 @@ immediate subdirectory with an `angular.json` or `tsconfig.json` is a workspace
|
||||
- **Hand-written HttpClient services** (`generator: httpclient`). A class method that calls `this.http.<verb><T>(url, body)`
|
||||
is the endpoint `<Service>.<method>`. The URL folds through constants and `environment.ts`
|
||||
(`${environment.apiUrl}/praemienkonto/branche-anlegen` → `/K3Data/api/v1/praemienkonto/branche-anlegen`).
|
||||
Scheme and host are dropped, and what does not fold stays `{name}`. `responseType` is the type argument,
|
||||
Scheme and host are dropped, and what does not fold stays `{name}`. An optional query string held in a
|
||||
`const` (`const param = t ? '?forcedTenant=' + t : ''; get(api + param)`) is the query, not a path
|
||||
segment: `…/login/spnego?forcedTenant={t}`, `queryParams: [forcedTenant]` (item 259). `responseType` is the type argument,
|
||||
and `requestType` is the body's type. A component's `this.verwaltungService.erstelleBranche(dto)` targets
|
||||
the endpoint (`calleeMethod`). K4 has 164 of them.
|
||||
- **A generated OpenAPI client that is not on disk** (`generator: openapi`). When a workspace's
|
||||
|
||||
@@ -187,6 +187,18 @@ analysis fell back to reading source. Gap ids L1–L6 refer to that file.
|
||||
Measured 2026-09-26 (server 399): `k3/soap-endpoints?port=WSK369` → `typesPackages`
|
||||
`rpc.entirex.softwareag.com.wsk369`, 4 clients (service, integration test, two test doubles) via wsimport + jaxb.
|
||||
|
||||
- [x] **259. An optional query string held in a variable becomes a path segment (L10 remainder)** (found 2026-09-26, done 2026-09-26)
|
||||
|
||||
`K3AuthModuleService.spnegoLogin` (k4-suche) calls `this.http.get(spnegoApi + param)` with
|
||||
`const param = forcedMandant ? '?forcedTenant=' + forcedMandant : ''`. The sidecar folded the URL to
|
||||
`/K3Koala/api/login/spnego{param}`, so the call did not pair with `GET /K3Koala/api/login/spnego`
|
||||
(`AuthenticationController.spnego`). Now a conditional with one branch `''` and the other starting with
|
||||
`?` or `&`, inline or as the initializer of a `const`, folds to the non-empty branch; the query is split
|
||||
off as before (`queryParams`). `let` variables and every other shape stay `{name}`.
|
||||
Measured 2026-09-26 (server 400, koala deep 35 s): `spnegoLogin` → `/K3Koala/api/login/spnego`, pairs with
|
||||
`at.k3.koala.security.AuthenticationController.spnego`; `koala/counterparts?kind=rest&unmatched=true` 9 → 8
|
||||
(3 unimplemented operations, 5 non-backend calls).
|
||||
|
||||
## High priority — comments, dead code and the counterpart relation (2026-08-27)
|
||||
|
||||
Three gaps reported after the `VermittlerController` reengineering round (nine UPMS services, four of
|
||||
|
||||
Reference in New Issue
Block a user