Skip to content

Commit 17e3c35

Browse files
arnaudArnaud Leymetardatan
authored
fix(openapi): HATEOAS href anonymization (#8641)
* fix(openapi): fix the HATEOAS href link anonymization - step 1: enrich the operations & make the impl limitations appear * fix(openapi): fix the HATEOAS href link anonymization - step 2: fix the anonymization implementation * chore(openapi): changeset * Update .changeset/social-pots-obey.md --------- Co-authored-by: Arnaud Leymet <aleymet@bouyguestelecom.fr> Co-authored-by: Arda TANRIKULU <ardatanrikulu@gmail.com>
1 parent 2dd9d03 commit 17e3c35

File tree

4 files changed

+45
-3
lines changed

4 files changed

+45
-3
lines changed

.changeset/social-pots-obey.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@omnigraph/openapi': patch
3+
---
4+
5+
When there are two operations in the OAS;
6+
`/products/` and `/products/{id}`
7+
Due to the bug in the logic, they conflict and the HATEOAS linking can choose the first one without any argument.

e2e/openapi-hateoas/__snapshots__/openapi-hateoas.test.ts.snap

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,24 @@ type Clothing implements Product @join__type(graph: PRODUCTS) @join__implements(
9999
}
100100
101101
type Query @extraSchemaDefinitionDirective(directives: {transport: [{subgraph: "Products", kind: "rest", location: "http://localhost:<OASService_port>"}]}) @extraSchemaDefinitionDirective(directives: {transport: [{subgraph: "Suppliers", kind: "rest", location: "http://localhost:<OASService_port>"}]}) @join__type(graph: PRODUCTS) @join__type(graph: SUPPLIERS) {
102+
"""Get all the products"""
103+
getProducts: ProductList @httpOperation(subgraph: "Products", path: "/products/", operationSpecificHeaders: [["accept", "application/json"]], httpMethod: GET) @join__field(graph: PRODUCTS)
102104
"""Get a product by ID"""
103105
getProductById(
104106
"""The product ID"""
105107
id: Int!
106-
): Product @httpOperation(subgraph: "Products", path: "/products/{args.id}", operationSpecificHeaders: [["accept", "application/json"]], httpMethod: GET) @linkResolver(subgraph: "Products", linkResolverMap: "{\\"self\\":{\\"linkObjArgs\\":{\\"id\\":\\"{root['id']}\\"},\\"targetTypeName\\":\\"Query\\",\\"targetFieldName\\":\\"getProductById\\"}}") @join__field(graph: PRODUCTS)
108+
): Product @httpOperation(subgraph: "Products", path: "/products/{args.id}", operationSpecificHeaders: [["accept", "application/json"]], httpMethod: GET) @linkResolver(subgraph: "Products", linkResolverMap: "{\\\"self\\\":{\\\"linkObjArgs\\\":{\\\"id\\\":\\\"{root['id']}\\\"},\\\"targetTypeName\\\":\\\"Query\\\",\\\"targetFieldName\\\":\\\"getProductById\\\"}}") @join__field(graph: PRODUCTS)
107109
"""Get a supplier by ID"""
108110
getSupplierById(
109111
"""The supplier ID"""
110112
id: Int!
111113
): Supplier @httpOperation(subgraph: "Suppliers", path: "/suppliers/{args.id}", operationSpecificHeaders: [["accept", "application/json"]], httpMethod: GET) @merge(subgraph: "Suppliers", keyField: "id", keyArg: "id") @join__field(graph: SUPPLIERS)
112114
}
113115
116+
type ProductList @join__type(graph: PRODUCTS) {
117+
items: [Product]
118+
}
119+
114120
interface Product @discriminator(subgraph: "Products", field: "_type", mapping: [["ELECTRONICS", "Electronics"], ["CLOTHING", "Clothing"]]) @join__type(graph: PRODUCTS) {
115121
_links: ProductLinks
116122
_type: String

e2e/openapi-hateoas/products.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55
"version": "1.0.0"
66
},
77
"paths": {
8+
"/products/": {
9+
"get": {
10+
"operationId": "getProducts",
11+
"summary": "Get all the products",
12+
"responses": {
13+
"200": {
14+
"description": "Success",
15+
"content": {
16+
"application/json": {
17+
"schema": {
18+
"$ref": "#/components/schemas/ProductList"
19+
}
20+
}
21+
}
22+
}
23+
}
24+
}
25+
},
826
"/products/{id}": {
927
"get": {
1028
"operationId": "getProductById",
@@ -37,6 +55,17 @@
3755
},
3856
"components": {
3957
"schemas": {
58+
"ProductList": {
59+
"type": "object",
60+
"properties": {
61+
"items": {
62+
"type": "array",
63+
"items": {
64+
"$ref": "#/components/schemas/Product"
65+
}
66+
}
67+
}
68+
},
4069
"Product": {
4170
"type": "object",
4271
"discriminator": {

packages/loaders/openapi/src/getJSONSchemaOptionsFromOpenAPIOptions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ export async function getJSONSchemaOptionsFromOpenAPIOptions(
595595
]?.find(link => link[hateOasConfig.linkNameIdentifier] === linkName);
596596
if (xLinkObj) {
597597
const xLinkHref = xLinkObj[hateOasConfig.linkPathIdentifier];
598-
const cleanXLinkHref = xLinkHref.replace(/{[^}]+}/g, '');
598+
const cleanXLinkHref = xLinkHref.replace(/{[^}]+}/g, '{}');
599599
const deferred = createDeferred<void>();
600600
function findActualOperationAndPath(
601601
possibleName: string,
@@ -605,7 +605,7 @@ export async function getJSONSchemaOptionsFromOpenAPIOptions(
605605
let actualOperation: OpenAPIV3.OperationObject;
606606
let actualPath: string;
607607
for (const path in possibleOasDoc.paths) {
608-
const cleanPath = path.replace(/{[^}]+}/g, '');
608+
const cleanPath = path.replace(/{[^}]+}/g, '{}');
609609
if (cleanPath === cleanXLinkHref) {
610610
actualPath = path;
611611
actualOperation = possibleOasDoc.paths[path][method];

0 commit comments

Comments
 (0)