Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 34 additions & 11 deletions src/ir/ir_symbol.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@

#include <sourcemeta/core/uri.h>

#include <algorithm> // std::ranges::reverse
#include <cassert> // assert
#include <sstream> // std::istringstream
#include <string> // std::string, std::getline
#include <vector> // std::vector
#include <algorithm> // std::ranges::reverse
#include <cassert> // assert
#include <filesystem> // std::filesystem::path
#include <sstream> // std::istringstream
#include <string> // std::string, std::getline
#include <vector> // std::vector

namespace {

// Strip all extensions from a filename (e.g., "user.schema.json" -> "user")
auto strip_extensions(const std::string &filename) -> std::string {
std::filesystem::path path{filename};
while (path.has_extension()) {
path = path.stem();
}
return path.string();
}

// If the input looks like an absolute URI, extract its path segments.
// For file URIs, only the filename (without extensions) is used.
// For other URIs, all path segments are used with extensions stripped from
// the last segment.
// Otherwise, add the input as a single segment.
// Note: segments are added in reverse order because the caller reverses
// the entire result at the end.
Expand All @@ -30,13 +43,23 @@ auto push_token_segments(std::vector<std::string> &result,
}
}

// Reverse segments since the caller will reverse the entire result
std::ranges::reverse(segments);
for (const auto &path_segment : segments) {
result.emplace_back(path_segment);
}
if (!segments.empty()) {
// Strip extensions from the last segment
segments.back() = strip_extensions(segments.back());

// For file URIs, only use the filename
if (uri.is_file()) {
result.emplace_back(segments.back());
} else {
// Reverse segments since the caller will reverse the entire result
std::ranges::reverse(segments);
for (const auto &path_segment : segments) {
result.emplace_back(path_segment);
}
}

return;
return;
}
}
}
// NOLINTNEXTLINE(bugprone-empty-catch)
Expand Down
18 changes: 9 additions & 9 deletions test/e2e/typescript/2020-12/bundled_schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,32 @@
"type": "object",
"required": [ "data", "meta" ],
"properties": {
"data": { "$ref": "https://example.com/schemas/user" },
"meta": { "$ref": "https://example.com/schemas/metadata" }
"data": { "$ref": "https://example.com/schemas/user.json" },
"meta": { "$ref": "https://example.com/schemas/metadata.json" }
},
"additionalProperties": false,
"$defs": {
"https://example.com/schemas/user": {
"https://example.com/schemas/user.json": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/user",
"$id": "https://example.com/schemas/user.json",
"type": "object",
"required": [ "id", "name" ],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "$ref": "https://example.com/schemas/email" }
"email": { "$ref": "https://example.com/schemas/email.schema.json" }
},
"additionalProperties": false
},
"https://example.com/schemas/email": {
"https://example.com/schemas/email.schema.json": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/email",
"$id": "https://example.com/schemas/email.schema.json",
"type": "string",
"format": "email"
},
"https://example.com/schemas/metadata": {
"https://example.com/schemas/metadata.json": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/metadata",
"$id": "https://example.com/schemas/metadata.json",
"type": "object",
"properties": {
"timestamp": { "type": "string" },
Expand Down
32 changes: 32 additions & 0 deletions test/e2e/typescript/2020-12/bundled_schema_file_uris/expected.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export type ResponseMeta = ResponseMetadata;

export type ResponseData = ResponseUser;

export type ResponseAdditionalProperties = never;

export type ResponseUserName = string;

export type ResponseUserId = number;

export type ResponseUserAdditionalProperties = never;

export interface ResponseUser {
"id": ResponseUserId;
"name": ResponseUserName;
}

export type ResponseMetadataVersion = number;

export type ResponseMetadataTimestamp = string;

export type ResponseMetadataAdditionalProperties = never;

export interface ResponseMetadata {
"timestamp"?: ResponseMetadataTimestamp;
"version"?: ResponseMetadataVersion;
}

export interface Response {
"data": ResponseData;
"meta": ResponseMeta;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"defaultPrefix": "Response"
}
34 changes: 34 additions & 0 deletions test/e2e/typescript/2020-12/bundled_schema_file_uris/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$id": "file:///schemas/api/response.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [ "data", "meta" ],
"properties": {
"data": { "$ref": "file:///schemas/models/user.json" },
"meta": { "$ref": "file:///schemas/models/metadata.json" }
},
"additionalProperties": false,
"$defs": {
"file:///schemas/models/user.json": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "file:///schemas/models/user.json",
"type": "object",
"required": [ "id", "name" ],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" }
},
"additionalProperties": false
},
"file:///schemas/models/metadata.json": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "file:///schemas/models/metadata.json",
"type": "object",
"properties": {
"timestamp": { "type": "string" },
"version": { "type": "integer" }
},
"additionalProperties": false
}
}
}
80 changes: 80 additions & 0 deletions test/e2e/typescript/2020-12/bundled_schema_file_uris/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {
Response,
ResponseUser,
ResponseMetadata
} from "./expected";


// Valid: full response with all fields
const fullResponse: Response = {
data: {
id: 123,
name: "John Doe"
},
meta: {
timestamp: "2024-01-15T10:30:00Z",
version: 1
}
};

// Valid: minimal response (required fields only)
const minimalResponse: Response = {
data: {
id: 1,
name: "Jane"
},
meta: {}
};

// Valid: user object directly
const user: ResponseUser = {
id: 42,
name: "Test User"
};

// Valid: metadata object
const metadata: ResponseMetadata = {
timestamp: "2024-01-15",
version: 2
};

// Invalid: missing required field 'data'
// @ts-expect-error - data is required
const missingData: Response = {
meta: {}
};

// Invalid: missing required field 'meta'
// @ts-expect-error - meta is required
const missingMeta: Response = {
data: { id: 1, name: "Test" }
};

// Invalid: user missing required 'id'
const userMissingId: Response = {
// @ts-expect-error - id is required on user
data: {
name: "Test"
},
meta: {}
};

// Invalid: user missing required 'name'
const userMissingName: Response = {
// @ts-expect-error - name is required on user
data: {
id: 1
},
meta: {}
};

// Invalid: extra property on user (additionalProperties: false)
const extraUserProp: Response = {
data: {
id: 1,
name: "Test",
// @ts-expect-error - extra property not allowed
extra: "not allowed"
},
meta: {}
};