Skip to content

Commit 1136d92

Browse files
authored
Improve handling of embedded resource $defs names (#14)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 30ca39b commit 1136d92

File tree

6 files changed

+192
-20
lines changed

6 files changed

+192
-20
lines changed

src/ir/ir_symbol.cc

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,28 @@
22

33
#include <sourcemeta/core/uri.h>
44

5-
#include <algorithm> // std::ranges::reverse
6-
#include <cassert> // assert
7-
#include <sstream> // std::istringstream
8-
#include <string> // std::string, std::getline
9-
#include <vector> // std::vector
5+
#include <algorithm> // std::ranges::reverse
6+
#include <cassert> // assert
7+
#include <filesystem> // std::filesystem::path
8+
#include <sstream> // std::istringstream
9+
#include <string> // std::string, std::getline
10+
#include <vector> // std::vector
1011

1112
namespace {
1213

14+
// Strip all extensions from a filename (e.g., "user.schema.json" -> "user")
15+
auto strip_extensions(const std::string &filename) -> std::string {
16+
std::filesystem::path path{filename};
17+
while (path.has_extension()) {
18+
path = path.stem();
19+
}
20+
return path.string();
21+
}
22+
1323
// If the input looks like an absolute URI, extract its path segments.
24+
// For file URIs, only the filename (without extensions) is used.
25+
// For other URIs, all path segments are used with extensions stripped from
26+
// the last segment.
1427
// Otherwise, add the input as a single segment.
1528
// Note: segments are added in reverse order because the caller reverses
1629
// the entire result at the end.
@@ -30,13 +43,23 @@ auto push_token_segments(std::vector<std::string> &result,
3043
}
3144
}
3245

33-
// Reverse segments since the caller will reverse the entire result
34-
std::ranges::reverse(segments);
35-
for (const auto &path_segment : segments) {
36-
result.emplace_back(path_segment);
37-
}
46+
if (!segments.empty()) {
47+
// Strip extensions from the last segment
48+
segments.back() = strip_extensions(segments.back());
49+
50+
// For file URIs, only use the filename
51+
if (uri.is_file()) {
52+
result.emplace_back(segments.back());
53+
} else {
54+
// Reverse segments since the caller will reverse the entire result
55+
std::ranges::reverse(segments);
56+
for (const auto &path_segment : segments) {
57+
result.emplace_back(path_segment);
58+
}
59+
}
3860

39-
return;
61+
return;
62+
}
4063
}
4164
}
4265
// NOLINTNEXTLINE(bugprone-empty-catch)

test/e2e/typescript/2020-12/bundled_schema/schema.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,32 @@
55
"type": "object",
66
"required": [ "data", "meta" ],
77
"properties": {
8-
"data": { "$ref": "https://example.com/schemas/user" },
9-
"meta": { "$ref": "https://example.com/schemas/metadata" }
8+
"data": { "$ref": "https://example.com/schemas/user.json" },
9+
"meta": { "$ref": "https://example.com/schemas/metadata.json" }
1010
},
1111
"additionalProperties": false,
1212
"$defs": {
13-
"https://example.com/schemas/user": {
13+
"https://example.com/schemas/user.json": {
1414
"$schema": "https://json-schema.org/draft/2020-12/schema",
15-
"$id": "https://example.com/schemas/user",
15+
"$id": "https://example.com/schemas/user.json",
1616
"type": "object",
1717
"required": [ "id", "name" ],
1818
"properties": {
1919
"id": { "type": "integer" },
2020
"name": { "type": "string" },
21-
"email": { "$ref": "https://example.com/schemas/email" }
21+
"email": { "$ref": "https://example.com/schemas/email.schema.json" }
2222
},
2323
"additionalProperties": false
2424
},
25-
"https://example.com/schemas/email": {
25+
"https://example.com/schemas/email.schema.json": {
2626
"$schema": "https://json-schema.org/draft/2020-12/schema",
27-
"$id": "https://example.com/schemas/email",
27+
"$id": "https://example.com/schemas/email.schema.json",
2828
"type": "string",
2929
"format": "email"
3030
},
31-
"https://example.com/schemas/metadata": {
31+
"https://example.com/schemas/metadata.json": {
3232
"$schema": "https://json-schema.org/draft/2020-12/schema",
33-
"$id": "https://example.com/schemas/metadata",
33+
"$id": "https://example.com/schemas/metadata.json",
3434
"type": "object",
3535
"properties": {
3636
"timestamp": { "type": "string" },
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export type ResponseMeta = ResponseMetadata;
2+
3+
export type ResponseData = ResponseUser;
4+
5+
export type ResponseAdditionalProperties = never;
6+
7+
export type ResponseUserName = string;
8+
9+
export type ResponseUserId = number;
10+
11+
export type ResponseUserAdditionalProperties = never;
12+
13+
export interface ResponseUser {
14+
"id": ResponseUserId;
15+
"name": ResponseUserName;
16+
}
17+
18+
export type ResponseMetadataVersion = number;
19+
20+
export type ResponseMetadataTimestamp = string;
21+
22+
export type ResponseMetadataAdditionalProperties = never;
23+
24+
export interface ResponseMetadata {
25+
"timestamp"?: ResponseMetadataTimestamp;
26+
"version"?: ResponseMetadataVersion;
27+
}
28+
29+
export interface Response {
30+
"data": ResponseData;
31+
"meta": ResponseMeta;
32+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"defaultPrefix": "Response"
3+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"$id": "file:///schemas/api/response.json",
3+
"$schema": "https://json-schema.org/draft/2020-12/schema",
4+
"type": "object",
5+
"required": [ "data", "meta" ],
6+
"properties": {
7+
"data": { "$ref": "file:///schemas/models/user.json" },
8+
"meta": { "$ref": "file:///schemas/models/metadata.json" }
9+
},
10+
"additionalProperties": false,
11+
"$defs": {
12+
"file:///schemas/models/user.json": {
13+
"$schema": "https://json-schema.org/draft/2020-12/schema",
14+
"$id": "file:///schemas/models/user.json",
15+
"type": "object",
16+
"required": [ "id", "name" ],
17+
"properties": {
18+
"id": { "type": "integer" },
19+
"name": { "type": "string" }
20+
},
21+
"additionalProperties": false
22+
},
23+
"file:///schemas/models/metadata.json": {
24+
"$schema": "https://json-schema.org/draft/2020-12/schema",
25+
"$id": "file:///schemas/models/metadata.json",
26+
"type": "object",
27+
"properties": {
28+
"timestamp": { "type": "string" },
29+
"version": { "type": "integer" }
30+
},
31+
"additionalProperties": false
32+
}
33+
}
34+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import {
2+
Response,
3+
ResponseUser,
4+
ResponseMetadata
5+
} from "./expected";
6+
7+
8+
// Valid: full response with all fields
9+
const fullResponse: Response = {
10+
data: {
11+
id: 123,
12+
name: "John Doe"
13+
},
14+
meta: {
15+
timestamp: "2024-01-15T10:30:00Z",
16+
version: 1
17+
}
18+
};
19+
20+
// Valid: minimal response (required fields only)
21+
const minimalResponse: Response = {
22+
data: {
23+
id: 1,
24+
name: "Jane"
25+
},
26+
meta: {}
27+
};
28+
29+
// Valid: user object directly
30+
const user: ResponseUser = {
31+
id: 42,
32+
name: "Test User"
33+
};
34+
35+
// Valid: metadata object
36+
const metadata: ResponseMetadata = {
37+
timestamp: "2024-01-15",
38+
version: 2
39+
};
40+
41+
// Invalid: missing required field 'data'
42+
// @ts-expect-error - data is required
43+
const missingData: Response = {
44+
meta: {}
45+
};
46+
47+
// Invalid: missing required field 'meta'
48+
// @ts-expect-error - meta is required
49+
const missingMeta: Response = {
50+
data: { id: 1, name: "Test" }
51+
};
52+
53+
// Invalid: user missing required 'id'
54+
const userMissingId: Response = {
55+
// @ts-expect-error - id is required on user
56+
data: {
57+
name: "Test"
58+
},
59+
meta: {}
60+
};
61+
62+
// Invalid: user missing required 'name'
63+
const userMissingName: Response = {
64+
// @ts-expect-error - name is required on user
65+
data: {
66+
id: 1
67+
},
68+
meta: {}
69+
};
70+
71+
// Invalid: extra property on user (additionalProperties: false)
72+
const extraUserProp: Response = {
73+
data: {
74+
id: 1,
75+
name: "Test",
76+
// @ts-expect-error - extra property not allowed
77+
extra: "not allowed"
78+
},
79+
meta: {}
80+
};

0 commit comments

Comments
 (0)