@conduit-client/model 3.9.0 → 3.10.0

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.
@@ -1,4 +1,5 @@
1
1
  import { type LoggerService, type FileParserLogger } from '@conduit-client/utils';
2
+ import type { Position } from '../source-position-map';
2
3
  import type { OpenAPIV3 } from 'openapi-types';
3
4
  import type { BaseAuraOperation, AuraOperationWithRequestBody, BaseOperation, HttpMethod, Request, Response, CacheStrategy, ConfigSchemaType, OperationType, ErrorStrategy, Binding, BaseHttpOperation, HttpOperationWithRequestBody, BaseGraphQLOperation } from '../../../api/endpoint';
4
5
  import type { SwaggerType, SwaggerTypeFactory } from '../types';
@@ -29,10 +30,7 @@ export declare abstract class SwaggerBaseOperation implements BaseOperation {
29
30
  protected operationSchemaBuilder: OperationSchemaBuilder;
30
31
  defaults: API['defaults'];
31
32
  serviceOverrides: ResolvedOperationServices;
32
- position: {
33
- line: number;
34
- column: number;
35
- };
33
+ position: Position;
36
34
  basePath: string;
37
35
  abstract readonly type: 'aura' | 'http';
38
36
  constructor(operation: OpenAPIV3.OperationObject, methodStr: string, swaggerTypeFactory: SwaggerTypeFactory, typeRegistry: TypeRegistry<SwaggerType>, logger: LoggerService, fileParserLogger: FileParserLogger, endpoint: SwaggerEndPoint, server: Server);
@@ -4,13 +4,17 @@
4
4
  export interface Position {
5
5
  line: number;
6
6
  column: number;
7
+ filePath: string;
7
8
  }
8
9
  /**
9
- * Maps JSON paths to their source positions in the YAML file.
10
+ * Maps JSON paths to their source positions in YAML files.
10
11
  * Used to provide accurate line/column information for validation errors.
12
+ * Supports both single-file and multi-file scenarios.
11
13
  */
12
14
  export declare class SourcePositionMap {
13
15
  private positions;
16
+ private fileUrls;
17
+ private refTargets;
14
18
  /**
15
19
  * Get the position for a given JSON path
16
20
  * @param jsonPath - The JSON path (e.g., "paths//users/get" or "components/schemas/User")
@@ -28,12 +32,40 @@ export declare class SourcePositionMap {
28
32
  * Get all stored paths
29
33
  */
30
34
  keys(): IterableIterator<string>;
35
+ /**
36
+ * Get all entries as [jsonPath, position] pairs
37
+ */
38
+ entries(): IterableIterator<[string, Position]>;
39
+ /**
40
+ * Get the number of files tracked in this map
41
+ */
42
+ get fileCount(): number;
43
+ /**
44
+ * Track a $ref target value and its position
45
+ */
46
+ setRefTarget(refValue: string, position: Position): void;
47
+ /**
48
+ * Find the position of a $ref that targets a file containing the given path,
49
+ * or an internal ref containing the given token.
50
+ */
51
+ findRefPosition(targetPath: string, token?: string): {
52
+ position: Position;
53
+ refValue: string;
54
+ } | undefined;
55
+ /**
56
+ * Merge positions from another map, setting filePath on each position.
57
+ * Used to aggregate positions from multiple files (e.g., external $refs).
58
+ * @param other - The source position map to merge from
59
+ * @param fileUrl - The file URL to associate with these positions
60
+ */
61
+ merge(other: SourcePositionMap, fileUrl: string): void;
31
62
  }
32
63
  /**
33
64
  * Builds a SourcePositionMap from YAML content by parsing it with the yaml package
34
65
  * which preserves source position information.
35
66
  *
36
67
  * @param yamlContent - The raw YAML content as a string
68
+ * @param fileUrl - The URL of the file being parsed
37
69
  * @returns A SourcePositionMap containing positions for operations and schemas
38
70
  */
39
- export declare function buildSourcePositionMap(yamlContent: string): SourcePositionMap;
71
+ export declare function buildSourcePositionMap(yamlContent: string, fileUrl: string): SourcePositionMap;
@@ -1,3 +1,4 @@
1
+ import type { Position } from '../source-position-map';
1
2
  import type { SwaggerTypeFactory } from './factory';
2
3
  import type { OpenAPIV3 } from 'openapi-types';
3
4
  import type { BaseType, Type, TypeExtensions, TypeRegistry } from '../../../types';
@@ -15,10 +16,7 @@ export declare abstract class SwaggerBaseType<T extends Type['type'], S extends
15
16
  resolved: boolean;
16
17
  abstract type: T;
17
18
  parsedExtensions: TypeExtensions;
18
- schemaPosition: {
19
- line: number;
20
- column: number;
21
- };
19
+ schemaPosition: Position;
22
20
  constructor(api: SwaggerAPI, schema: S, schemaName: string | undefined, typeRegistry: TypeRegistry<SwaggerType>, factory: SwaggerTypeFactory, logger: LoggerService, fileParserLogger: FileParserLogger, jsonPath?: string);
23
21
  resolve(): void;
24
22
  typeResolve(): void;
package/dist/v1/index.js CHANGED
@@ -7,6 +7,7 @@ import * as url from "url";
7
7
  import amf from "amf-client-js";
8
8
  import path from "path";
9
9
  import SwaggerParser from "@apidevtools/swagger-parser";
10
+ import { MissingPointerError, ResolverError } from "@apidevtools/json-schema-ref-parser";
10
11
  const BindingTypesEnum = ["wire", "imperative", "imperative-legacy", "mutation"];
11
12
  class BaseTypeRegistry extends Map {
12
13
  nameOf(t) {
@@ -13338,6 +13339,7 @@ const annotationSchema = z.object({
13338
13339
  });
13339
13340
  class SwaggerBaseType {
13340
13341
  constructor(api, schema2, schemaName, typeRegistry, factory, logger, fileParserLogger, jsonPath) {
13342
+ var _a;
13341
13343
  this.api = api;
13342
13344
  this.schema = schema2;
13343
13345
  this.schemaName = schemaName;
@@ -13347,13 +13349,18 @@ class SwaggerBaseType {
13347
13349
  this.fileParserLogger = fileParserLogger;
13348
13350
  this.resolved = false;
13349
13351
  this.parsedExtensions = { type: "unidentifiable" };
13352
+ const defaultPosition = {
13353
+ line: 0,
13354
+ column: 0,
13355
+ filePath: ((_a = api.sourceUrl) == null ? void 0 : _a.pathname) ?? ""
13356
+ };
13350
13357
  if (jsonPath) {
13351
- this.schemaPosition = api.getPosition(jsonPath) ?? { line: 0, column: 0 };
13358
+ this.schemaPosition = api.getPosition(jsonPath) ?? defaultPosition;
13352
13359
  } else if (schemaName) {
13353
13360
  const defaultPath = `components/schemas/${schemaName}`;
13354
- this.schemaPosition = api.getPosition(defaultPath) ?? { line: 0, column: 0 };
13361
+ this.schemaPosition = api.getPosition(defaultPath) ?? defaultPosition;
13355
13362
  } else {
13356
- this.schemaPosition = { line: 0, column: 0 };
13363
+ this.schemaPosition = defaultPosition;
13357
13364
  }
13358
13365
  }
13359
13366
  resolve() {
@@ -14133,7 +14140,7 @@ function swaggerTypeFactory(api, schema2, schemaName, typeRegistry, logger, file
14133
14140
  }
14134
14141
  class SwaggerBaseOperation {
14135
14142
  constructor(operation, methodStr, swaggerTypeFactory2, typeRegistry, logger, fileParserLogger, endpoint, server) {
14136
- var _a, _b;
14143
+ var _a, _b, _c;
14137
14144
  this.operation = operation;
14138
14145
  this.methodStr = methodStr;
14139
14146
  this.swaggerTypeFactory = swaggerTypeFactory2;
@@ -14145,7 +14152,12 @@ class SwaggerBaseOperation {
14145
14152
  this.defaults = endpoint.api.defaults;
14146
14153
  this.method = methodStr.toUpperCase();
14147
14154
  const jsonPath = `paths/${endpoint.path}/${methodStr}`;
14148
- this.position = ((_b = (_a = endpoint.api).getPosition) == null ? void 0 : _b.call(_a, jsonPath)) ?? { line: 0, column: 0 };
14155
+ const defaultPosition = {
14156
+ line: 0,
14157
+ column: 0,
14158
+ filePath: ((_a = endpoint.api.sourceUrl) == null ? void 0 : _a.pathname) ?? "Unknown File"
14159
+ };
14160
+ this.position = ((_c = (_b = endpoint.api).getPosition) == null ? void 0 : _c.call(_b, jsonPath)) ?? defaultPosition;
14149
14161
  const extensionsRaw = extractExtensions(operation);
14150
14162
  this.operationId = operation.operationId;
14151
14163
  this.operationSchemaBuilder = new OperationSchemaBuilder(
@@ -14687,46 +14699,42 @@ ${validationMessage}`);
14687
14699
  }
14688
14700
  function getSchemaPosition(subject, validationType) {
14689
14701
  if (validationType === ValidationType.Operation) {
14690
- return subject.position || { line: 0, column: 0 };
14702
+ return subject.position || { line: 0, column: 0, filePath: "" };
14691
14703
  }
14692
- return subject.schemaPosition || { line: 0, column: 0 };
14704
+ return subject.schemaPosition || { line: 0, column: 0, filePath: "" };
14693
14705
  }
14694
- function formatMessage(messages, lineFormatter2, level = 0) {
14695
- const padding = Array(4 * level).fill(" ").join("");
14706
+ function formatMessage(messages, level = 0) {
14707
+ const padding = " ".repeat(level);
14696
14708
  return messages.reduce((result, message) => {
14697
14709
  const position = getSchemaPosition(message.subject, message.validationType);
14698
- return `${result}${padding}- ${lineFormatter2(position, message.message)}
14699
- ${formatMessage(message.subValidationMessages ?? [], lineFormatter2, level + 1)}`;
14710
+ const formatted = lineFormatter(position, message.message, position.filePath);
14711
+ const subMessages = formatMessage(message.subValidationMessages ?? [], level + 1);
14712
+ return `${result}${padding}- ${formatted}
14713
+ ${subMessages}`;
14700
14714
  }, "");
14701
14715
  }
14702
14716
  function isValidSwaggerAPI(api, fileParserLogger) {
14703
- const formatter = (pos, msg) => {
14704
- return lineFormatter(pos, msg, fileParserLogger.filePath);
14705
- };
14717
+ const defaultFilePath = fileParserLogger.filePath;
14706
14718
  const apiValidator = buildApiValidatorFor(api);
14707
14719
  const result = apiValidator.validate(api);
14708
14720
  const isValid2 = result.isOk();
14709
14721
  const messages = isValid2 ? result.value : result.error;
14710
- for (const {
14711
- message,
14712
- subject,
14713
- severity,
14714
- validationType,
14715
- subValidationMessages: extraValidation
14716
- } of messages) {
14717
- let lineMessage = message;
14718
- if (extraValidation !== void 0) {
14719
- const formattedValidation = `${formatMessage(extraValidation, formatter, 1)}`;
14720
- lineMessage = `${lineMessage}
14721
- ${formattedValidation}`;
14722
- }
14722
+ for (const { message, subject, severity, validationType, subValidationMessages } of messages) {
14723
14723
  const position = getSchemaPosition(subject, validationType);
14724
+ let finalMessage = message;
14725
+ if (subValidationMessages !== void 0 && subValidationMessages.length > 0) {
14726
+ finalMessage += `
14727
+ ${formatMessage(subValidationMessages, 1)}`;
14728
+ }
14729
+ if (position.filePath !== defaultFilePath) {
14730
+ finalMessage = `[${position.filePath}:${position.line}:${position.column}] ${finalMessage}`;
14731
+ }
14724
14732
  if (severity === ValidationSeverity.Error) {
14725
- fileParserLogger.error(position, lineMessage);
14733
+ fileParserLogger.error(position, finalMessage);
14726
14734
  } else if (severity === ValidationSeverity.Warning) {
14727
- fileParserLogger.warn(position, lineMessage);
14735
+ fileParserLogger.warn(position, finalMessage);
14728
14736
  } else if (severity === ValidationSeverity.Info) {
14729
- fileParserLogger.info(position, lineMessage);
14737
+ fileParserLogger.info(position, finalMessage);
14730
14738
  }
14731
14739
  }
14732
14740
  return isValid2;
@@ -14924,6 +14932,8 @@ servers:
14924
14932
  class SourcePositionMap {
14925
14933
  constructor() {
14926
14934
  this.positions = /* @__PURE__ */ new Map();
14935
+ this.fileUrls = /* @__PURE__ */ new Set();
14936
+ this.refTargets = /* @__PURE__ */ new Map();
14927
14937
  }
14928
14938
  /**
14929
14939
  * Get the position for a given JSON path
@@ -14950,10 +14960,71 @@ class SourcePositionMap {
14950
14960
  keys() {
14951
14961
  return this.positions.keys();
14952
14962
  }
14963
+ /**
14964
+ * Get all entries as [jsonPath, position] pairs
14965
+ */
14966
+ entries() {
14967
+ return this.positions.entries();
14968
+ }
14969
+ /**
14970
+ * Get the number of files tracked in this map
14971
+ */
14972
+ get fileCount() {
14973
+ return this.fileUrls.size;
14974
+ }
14975
+ /**
14976
+ * Track a $ref target value and its position
14977
+ */
14978
+ setRefTarget(refValue, position) {
14979
+ this.refTargets.set(refValue, position);
14980
+ }
14981
+ /**
14982
+ * Find the position of a $ref that targets a file containing the given path,
14983
+ * or an internal ref containing the given token.
14984
+ */
14985
+ findRefPosition(targetPath, token) {
14986
+ const targetFileName = targetPath.split("/").pop() || targetPath;
14987
+ for (const [refValue, position] of this.refTargets) {
14988
+ if (refValue.includes(targetFileName)) {
14989
+ return { position, refValue };
14990
+ }
14991
+ if (token && refValue.startsWith("#/") && refValue.includes(token)) {
14992
+ return { position, refValue };
14993
+ }
14994
+ }
14995
+ }
14996
+ /**
14997
+ * Merge positions from another map, setting filePath on each position.
14998
+ * Used to aggregate positions from multiple files (e.g., external $refs).
14999
+ * @param other - The source position map to merge from
15000
+ * @param fileUrl - The file URL to associate with these positions
15001
+ */
15002
+ merge(other, fileUrl) {
15003
+ const filePath = extractFilePath(fileUrl);
15004
+ this.fileUrls.add(fileUrl);
15005
+ for (const [jsonPath, position] of other.entries()) {
15006
+ this.positions.set(jsonPath, {
15007
+ ...position,
15008
+ filePath
15009
+ });
15010
+ }
15011
+ for (const [refValue, position] of other.refTargets) {
15012
+ this.refTargets.set(refValue, { ...position, filePath });
15013
+ }
15014
+ }
15015
+ }
15016
+ function extractFilePath(fileUrl) {
15017
+ try {
15018
+ const url2 = new URL(fileUrl);
15019
+ return url2.pathname;
15020
+ } catch {
15021
+ return fileUrl;
15022
+ }
14953
15023
  }
14954
- function buildSourcePositionMap(yamlContent) {
15024
+ function buildSourcePositionMap(yamlContent, fileUrl) {
14955
15025
  const positionMap = new SourcePositionMap();
14956
15026
  const lineCounter = new LineCounter();
15027
+ const filePath = extractFilePath(fileUrl);
14957
15028
  const doc = parseDocument(yamlContent, { lineCounter });
14958
15029
  if (!doc.contents || !isMap(doc.contents)) {
14959
15030
  return positionMap;
@@ -14965,7 +15036,15 @@ function buildSourcePositionMap(yamlContent) {
14965
15036
  const range = pair.key.range;
14966
15037
  if (range) {
14967
15038
  const pos = lineCounter.linePos(range[0]);
14968
- positionMap.set(jsonPath, { line: pos.line, column: pos.col - 1 });
15039
+ positionMap.set(jsonPath, { line: pos.line, column: pos.col - 1, filePath });
15040
+ if (pair.key.value === "$ref" && pair.value && isScalar(pair.value)) {
15041
+ const refValue = String(pair.value.value);
15042
+ positionMap.setRefTarget(refValue, {
15043
+ line: pos.line,
15044
+ column: pos.col - 1,
15045
+ filePath
15046
+ });
15047
+ }
14969
15048
  }
14970
15049
  }
14971
15050
  }
@@ -14988,6 +15067,9 @@ function buildJsonPath(pair, path2) {
14988
15067
  if (segments.length >= 3 && segments[0] === "components" && segments[1] === "schemas") {
14989
15068
  return segments.join("/");
14990
15069
  }
15070
+ if (segments.length >= 1 && !["openapi", "info", "servers", "paths", "components", "x-onestore"].includes(segments[0])) {
15071
+ return segments.join("/");
15072
+ }
14991
15073
  return void 0;
14992
15074
  }
14993
15075
  const positionMapCache = /* @__PURE__ */ new Map();
@@ -14996,7 +15078,7 @@ const positionTrackingParser = {
14996
15078
  canParse: [".yaml", ".yml"],
14997
15079
  async parse(file) {
14998
15080
  const content = Buffer.isBuffer(file.data) ? file.data.toString() : file.data;
14999
- const positionMap = buildSourcePositionMap(content);
15081
+ const positionMap = buildSourcePositionMap(content, file.url);
15000
15082
  positionMapCache.set(file.url, positionMap);
15001
15083
  return parse(content);
15002
15084
  }
@@ -15012,12 +15094,23 @@ const PARSER_OPTIONS = {
15012
15094
  // Still validate $ref resolution
15013
15095
  }
15014
15096
  };
15097
+ function findRefPositionInCache(targetPath, token) {
15098
+ for (const positionMap of positionMapCache.values()) {
15099
+ const result = positionMap.findRefPosition(targetPath, token);
15100
+ if (result) {
15101
+ return result;
15102
+ }
15103
+ }
15104
+ }
15015
15105
  async function parseSwaggerDocument(source, logger) {
15016
15106
  const sourceUrl = source.toString();
15017
15107
  try {
15018
15108
  const api = await SwaggerParser.bundle(sourceUrl, PARSER_OPTIONS);
15019
- const positionMap = positionMapCache.get(sourceUrl) ?? new SourcePositionMap();
15020
- positionMapCache.delete(sourceUrl);
15109
+ const positionMap = new SourcePositionMap();
15110
+ for (const [fileUrl, filePositionMap] of positionMapCache) {
15111
+ positionMap.merge(filePositionMap, fileUrl);
15112
+ }
15113
+ positionMapCache.clear();
15021
15114
  if (!("openapi" in api) || !api.openapi.startsWith("3.")) {
15022
15115
  throw new Error(
15023
15116
  `Only OpenAPI 3.x documents are supported. Found version: ${api.openapi || api.swagger || "unknown"}`
@@ -15028,7 +15121,46 @@ async function parseSwaggerDocument(source, logger) {
15028
15121
  positionMap
15029
15122
  };
15030
15123
  } catch (error) {
15031
- positionMapCache.delete(sourceUrl);
15124
+ if (error instanceof MissingPointerError) {
15125
+ const missingFile = error.source;
15126
+ const tokenMatch = error.message.match(/Token "([^"]+)"/);
15127
+ const token = (tokenMatch == null ? void 0 : tokenMatch[1]) ?? "unknown";
15128
+ const refInfo = findRefPositionInCache(missingFile, token);
15129
+ const filename = missingFile.split("/").pop() ?? missingFile;
15130
+ if (refInfo) {
15131
+ const { position, refValue } = refInfo;
15132
+ logger.error(
15133
+ position,
15134
+ `Cannot resolve reference '${refValue}': token '${token}' does not exist in '${filename}'`
15135
+ );
15136
+ } else {
15137
+ logger.error(
15138
+ { line: 0, column: 0 },
15139
+ `Cannot resolve reference: token '${token}' does not exist in '${filename}'`
15140
+ );
15141
+ }
15142
+ positionMapCache.clear();
15143
+ throw error;
15144
+ }
15145
+ if (error instanceof ResolverError) {
15146
+ const missingFile = error.source;
15147
+ const refInfo = findRefPositionInCache(missingFile);
15148
+ if (refInfo) {
15149
+ const { position, refValue } = refInfo;
15150
+ logger.error(
15151
+ position,
15152
+ `Cannot resolve external reference '${refValue}': file not found '${missingFile}'`
15153
+ );
15154
+ } else {
15155
+ logger.error(
15156
+ { line: 0, column: 0 },
15157
+ `Cannot resolve reference '${missingFile}': file not found`
15158
+ );
15159
+ }
15160
+ positionMapCache.clear();
15161
+ throw error;
15162
+ }
15163
+ positionMapCache.clear();
15032
15164
  const message = error instanceof Error ? error.message : String(error);
15033
15165
  logger.error(
15034
15166
  { line: 0, column: 0 },