@haste-health/client 0.126.27 → 0.126.29

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.
package/lib/url.d.ts CHANGED
@@ -1,11 +1,25 @@
1
1
  import { FHIR_VERSION, Resource } from "@haste-health/fhir-types/versions";
2
2
  type SPECIAL_CHARACTER = "\\" | "|" | "$" | ",";
3
3
  /**
4
- * Returns string with split pieces and unescapes special characters from the split piece.
4
+ * Splits on each separator not escaped by a backslash, leaving the pieces' escapes in place.
5
+ * A value without a backslash takes the plain split.
5
6
  * @param parameter Parameter to be split
7
+ * @param separator One of special characters that get escaped on parameter.
8
+ */
9
+ export declare function splitUnescaped(parameter: string, separator: SPECIAL_CHARACTER): string[];
10
+ /**
11
+ * A value's segments between unescaped separators, each with its escapes resolved:
12
+ * `system|code` of a token. The counterpart of `Parameter::segmented` in the Rust client.
13
+ * @param parameter Parameter value as written in the URL
6
14
  * @param specialCharacter One of special characters that get escaped on parameter.
7
15
  */
8
16
  export declare function splitParameter(parameter: string, specialCharacter: SPECIAL_CHARACTER): string[];
17
+ /**
18
+ * A parameter's values with their escapes resolved. The counterpart of
19
+ * `Parameter::values` in the Rust client.
20
+ * @param parameter A parsed parameter, whose values are as written in the URL.
21
+ */
22
+ export declare function parameterValues(parameter: ParsedParameter<string>): string[];
9
23
  /**
10
24
  * Escapes a parameter values special characters
11
25
  * Reference: https://hl7.org/fhir/R4/search.html#escaping
@@ -22,6 +36,11 @@ export declare function escapeParameter(parameter: string): string;
22
36
  export declare function unescapeParameter(parameter: string): string;
23
37
  export interface ParsedParameter<T> {
24
38
  name: string;
39
+ /**
40
+ * The values as written after `=`, one per unescaped comma, with their escapes
41
+ * (`\,` `\|` `\$` `\\`) still in place: which of `|` and `$` separate is only known
42
+ * with the parameter's type. Read them through `parameterValues` or `splitParameter`.
43
+ */
25
44
  value: T[];
26
45
  modifier?: string;
27
46
  chains?: string[];
package/lib/url.js CHANGED
@@ -1,23 +1,43 @@
1
1
  import { OperationError, outcomeError } from "@haste-health/operation-outcomes";
2
2
  const SPECIAL_CHARACTERS = ["\\", "|", "$", ","];
3
3
  /**
4
- * Returns string with split pieces and unescapes special characters from the split piece.
4
+ * Splits on each separator not escaped by a backslash, leaving the pieces' escapes in place.
5
+ * A value without a backslash takes the plain split.
5
6
  * @param parameter Parameter to be split
6
- * @param specialCharacter One of special characters that get escaped on parameter.
7
+ * @param separator One of special characters that get escaped on parameter.
7
8
  */
8
- export function splitParameter(parameter, specialCharacter) {
9
- const specialCharEg = new RegExp(`\\${specialCharacter}`, "g");
10
- let prevIndex = -1;
9
+ export function splitUnescaped(parameter, separator) {
10
+ if (!parameter.includes("\\"))
11
+ return parameter.split(separator);
11
12
  const pieces = [];
12
- let match;
13
- while ((match = specialCharEg.exec(parameter))) {
14
- if (match.index === 0 || parameter[match.index - 1] !== "\\") {
15
- pieces.push(parameter.substring(prevIndex + 1, match.index));
16
- prevIndex = match.index;
13
+ let start = 0;
14
+ for (let i = 0; i < parameter.length; i++) {
15
+ if (parameter[i] === "\\")
16
+ i++;
17
+ else if (parameter[i] === separator) {
18
+ pieces.push(parameter.substring(start, i));
19
+ start = i + 1;
17
20
  }
18
21
  }
19
- pieces.push(parameter.substring(prevIndex + 1));
20
- return pieces.map(unescapeParameter);
22
+ pieces.push(parameter.substring(start));
23
+ return pieces;
24
+ }
25
+ /**
26
+ * A value's segments between unescaped separators, each with its escapes resolved:
27
+ * `system|code` of a token. The counterpart of `Parameter::segmented` in the Rust client.
28
+ * @param parameter Parameter value as written in the URL
29
+ * @param specialCharacter One of special characters that get escaped on parameter.
30
+ */
31
+ export function splitParameter(parameter, specialCharacter) {
32
+ return splitUnescaped(parameter, specialCharacter).map((piece) => unescapeParameter(piece));
33
+ }
34
+ /**
35
+ * A parameter's values with their escapes resolved. The counterpart of
36
+ * `Parameter::values` in the Rust client.
37
+ * @param parameter A parsed parameter, whose values are as written in the URL.
38
+ */
39
+ export function parameterValues(parameter) {
40
+ return parameter.value.map((value) => unescapeParameter(value));
21
41
  }
22
42
  /**
23
43
  * Escapes a parameter values special characters
@@ -37,9 +57,12 @@ export function escapeParameter(parameter) {
37
57
  * @returns Unescaped Parameter
38
58
  */
39
59
  export function unescapeParameter(parameter) {
40
- return SPECIAL_CHARACTERS.reduce((parameter, character) => {
41
- return parameter.replaceAll(`\\${character}`, character);
42
- }, parameter);
60
+ if (!parameter.includes("\\"))
61
+ return parameter;
62
+ // Escapes are read left to right so `\\,` is a backslash then a separator.
63
+ return parameter.replace(/\\([\s\S])/g, (escape, character) => SPECIAL_CHARACTERS.includes(character)
64
+ ? character
65
+ : escape);
43
66
  }
44
67
  /**
45
68
  * Given a query string create complex FHIR Query object.
@@ -58,7 +81,8 @@ export function parseQuery(queryParams) {
58
81
  const searchParam = {
59
82
  name,
60
83
  modifier,
61
- value: value.split(",").map((v) => decodeURIComponent(v)),
84
+ // The pieces keep their escapes; see ParsedParameter.value.
85
+ value: splitUnescaped(value, ",").map((v) => decodeURIComponent(v)),
62
86
  };
63
87
  if (chains.length > 1)
64
88
  searchParam.chains = chains.slice(1);
package/lib/url.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"url.js","sourceRoot":"","sources":["../src/url.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../src/url.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haste-health/client",
3
- "version": "0.126.27",
3
+ "version": "0.126.29",
4
4
  "homepage": "https://haste.health",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,11 +27,11 @@
27
27
  "jest": "^29.7.0",
28
28
  "ts-jest": "^29.4.11",
29
29
  "typescript": "6.0.3",
30
- "@haste-health/fhir-types": "^0.126.27"
30
+ "@haste-health/fhir-types": "^0.126.29"
31
31
  },
32
32
  "dependencies": {
33
- "@haste-health/operation-execution": "^0.126.27",
34
- "@haste-health/operation-outcomes": "^0.126.27"
33
+ "@haste-health/operation-execution": "^0.126.29",
34
+ "@haste-health/operation-outcomes": "^0.126.29"
35
35
  },
36
36
  "files": [
37
37
  "lib/**",
package/src/url.test.ts CHANGED
@@ -2,7 +2,9 @@ import { expect, test } from "@jest/globals";
2
2
 
3
3
  import parseFHIRSearch, {
4
4
  escapeParameter,
5
+ parameterValues,
5
6
  splitParameter,
7
+ splitUnescaped,
6
8
  unescapeParameter,
7
9
  } from "./url.js";
8
10
 
@@ -50,3 +52,39 @@ test("TEST Parameter Split", () => {
50
52
  "456",
51
53
  ]);
52
54
  });
55
+
56
+ test("TEST escaped separators", () => {
57
+ expect(splitUnescaped("a\\,b,c", ",")).toEqual(["a\\,b", "c"]);
58
+ expect(splitUnescaped("a\\\\,b", ",")).toEqual(["a\\\\", "b"]);
59
+ expect(unescapeParameter("a\\\\,b")).toEqual("a\\,b");
60
+ expect(unescapeParameter("a\\,b\\|c\\$d")).toEqual("a,b|c$d");
61
+ expect(unescapeParameter("a\\xb")).toEqual("a\\xb");
62
+ });
63
+
64
+ test("TEST query comma escaping keeps the pieces as written", () => {
65
+ expect(parseFHIRSearch("Patient?name=Smith\\,Jones")[0].value).toEqual([
66
+ "Smith\\,Jones",
67
+ ]);
68
+ expect(parseFHIRSearch("Patient?name=Smith,Jones")[0].value).toEqual([
69
+ "Smith",
70
+ "Jones",
71
+ ]);
72
+ expect(parseFHIRSearch("Patient?identifier=a\\|b|1,c")[0].value).toEqual([
73
+ "a\\|b|1",
74
+ "c",
75
+ ]);
76
+ expect(parseFHIRSearch("Patient?name=a\\\\,b")[0].value).toEqual([
77
+ "a\\\\",
78
+ "b",
79
+ ]);
80
+ });
81
+
82
+ test("TEST accessors resolve the escapes", () => {
83
+ const [name] = parseFHIRSearch("Patient?name=Smith\\,Jones,a\\\\b");
84
+ expect(parameterValues(name)).toEqual(["Smith,Jones", "a\\b"]);
85
+ const [identifier] = parseFHIRSearch("Patient?identifier=http://a\\|b|1,c");
86
+ expect(identifier.value.map((v) => splitParameter(v, "|"))).toEqual([
87
+ ["http://a|b", "1"],
88
+ ["c"],
89
+ ]);
90
+ });
package/src/url.ts CHANGED
@@ -5,28 +5,51 @@ type SPECIAL_CHARACTER = "\\" | "|" | "$" | ",";
5
5
  const SPECIAL_CHARACTERS: SPECIAL_CHARACTER[] = ["\\", "|", "$", ","];
6
6
 
7
7
  /**
8
- * Returns string with split pieces and unescapes special characters from the split piece.
8
+ * Splits on each separator not escaped by a backslash, leaving the pieces' escapes in place.
9
+ * A value without a backslash takes the plain split.
9
10
  * @param parameter Parameter to be split
10
- * @param specialCharacter One of special characters that get escaped on parameter.
11
+ * @param separator One of special characters that get escaped on parameter.
11
12
  */
12
- export function splitParameter(
13
+ export function splitUnescaped(
13
14
  parameter: string,
14
- specialCharacter: SPECIAL_CHARACTER
15
+ separator: SPECIAL_CHARACTER,
15
16
  ): string[] {
16
- const specialCharEg = new RegExp(`\\${specialCharacter}`, "g");
17
- let prevIndex = -1;
18
- const pieces = [];
19
- let match;
20
-
21
- while ((match = specialCharEg.exec(parameter))) {
22
- if (match.index === 0 || parameter[match.index - 1] !== "\\") {
23
- pieces.push(parameter.substring(prevIndex + 1, match.index));
24
- prevIndex = match.index;
17
+ if (!parameter.includes("\\")) return parameter.split(separator);
18
+ const pieces: string[] = [];
19
+ let start = 0;
20
+ for (let i = 0; i < parameter.length; i++) {
21
+ if (parameter[i] === "\\") i++;
22
+ else if (parameter[i] === separator) {
23
+ pieces.push(parameter.substring(start, i));
24
+ start = i + 1;
25
25
  }
26
26
  }
27
- pieces.push(parameter.substring(prevIndex + 1));
27
+ pieces.push(parameter.substring(start));
28
+ return pieces;
29
+ }
28
30
 
29
- return pieces.map(unescapeParameter);
31
+ /**
32
+ * A value's segments between unescaped separators, each with its escapes resolved:
33
+ * `system|code` of a token. The counterpart of `Parameter::segmented` in the Rust client.
34
+ * @param parameter Parameter value as written in the URL
35
+ * @param specialCharacter One of special characters that get escaped on parameter.
36
+ */
37
+ export function splitParameter(
38
+ parameter: string,
39
+ specialCharacter: SPECIAL_CHARACTER,
40
+ ): string[] {
41
+ return splitUnescaped(parameter, specialCharacter).map((piece) =>
42
+ unescapeParameter(piece),
43
+ );
44
+ }
45
+
46
+ /**
47
+ * A parameter's values with their escapes resolved. The counterpart of
48
+ * `Parameter::values` in the Rust client.
49
+ * @param parameter A parsed parameter, whose values are as written in the URL.
50
+ */
51
+ export function parameterValues(parameter: ParsedParameter<string>): string[] {
52
+ return parameter.value.map((value) => unescapeParameter(value));
30
53
  }
31
54
 
32
55
  /**
@@ -40,7 +63,7 @@ export function escapeParameter(parameter: string): string {
40
63
  (parameter: string, character: string): string => {
41
64
  return parameter.replaceAll(character, `\\${character}`);
42
65
  },
43
- parameter
66
+ parameter,
44
67
  );
45
68
  }
46
69
 
@@ -51,16 +74,22 @@ export function escapeParameter(parameter: string): string {
51
74
  * @returns Unescaped Parameter
52
75
  */
53
76
  export function unescapeParameter(parameter: string): string {
54
- return SPECIAL_CHARACTERS.reduce(
55
- (parameter: string, character: string): string => {
56
- return parameter.replaceAll(`\\${character}`, character);
57
- },
58
- parameter
77
+ if (!parameter.includes("\\")) return parameter;
78
+ // Escapes are read left to right so `\\,` is a backslash then a separator.
79
+ return parameter.replace(/\\([\s\S])/g, (escape, character) =>
80
+ (SPECIAL_CHARACTERS as readonly string[]).includes(character)
81
+ ? character
82
+ : escape,
59
83
  );
60
84
  }
61
85
 
62
86
  export interface ParsedParameter<T> {
63
87
  name: string;
88
+ /**
89
+ * The values as written after `=`, one per unescaped comma, with their escapes
90
+ * (`\,` `\|` `\$` `\\`) still in place: which of `|` and `$` separate is only known
91
+ * with the parameter's type. Read them through `parameterValues` or `splitParameter`.
92
+ */
64
93
  value: T[];
65
94
  modifier?: string;
66
95
  chains?: string[];
@@ -92,7 +121,7 @@ export type Parameters<Version extends FHIR_VERSION> =
92
121
  * @returns Record of parsed parameters with name modifier and value.
93
122
  */
94
123
  export function parseQuery(
95
- queryParams: string | undefined
124
+ queryParams: string | undefined,
96
125
  ): ParsedParameter<string>[] {
97
126
  const parameters = !queryParams
98
127
  ? []
@@ -102,7 +131,7 @@ export function parseQuery(
102
131
  .reduce(
103
132
  (
104
133
  parameters,
105
- [key, value]
134
+ [key, value],
106
135
  ): Record<string, ParsedParameter<string>> => {
107
136
  const chains = key.split(".");
108
137
 
@@ -111,7 +140,10 @@ export function parseQuery(
111
140
  const searchParam: ParsedParameter<string> = {
112
141
  name,
113
142
  modifier,
114
- value: value.split(",").map((v) => decodeURIComponent(v)),
143
+ // The pieces keep their escapes; see ParsedParameter.value.
144
+ value: splitUnescaped(value, ",").map((v) =>
145
+ decodeURIComponent(v),
146
+ ),
115
147
  };
116
148
 
117
149
  if (chains.length > 1) searchParam.chains = chains.slice(1);
@@ -119,7 +151,7 @@ export function parseQuery(
119
151
 
120
152
  return { ...parameters, [searchParam.name]: searchParam };
121
153
  },
122
- {}
154
+ {},
123
155
  );
124
156
 
125
157
  return Object.values(parameters);
@@ -131,7 +163,7 @@ export function parseQuery(
131
163
  * @returns Record of parsed parameters with name modifier and value.
132
164
  */
133
165
  export default function parseUrl(
134
- url: string
166
+ url: string,
135
167
  ): ParsedParameter<string | number>[] {
136
168
  const chunks = url.split("?");
137
169
  if (chunks.length > 2)