@cef-ebsi/ebsi-uri 0.1.1-alpha.0 → 0.2.0-alpha.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.
package/README.md CHANGED
@@ -44,6 +44,28 @@ returns
44
44
  }
45
45
  ```
46
46
 
47
+ ### Parse URL
48
+
49
+ The `parseUrl` function takes an URL (string) and returns the URI components `network`, `service`, and `resource`. The function accepts an optional object that contains the list of supported services with their version number, and will throw an error if the URL contains a service or service version that is not supported.
50
+
51
+ Example:
52
+
53
+ ```js
54
+ parseUrl(
55
+ "https://api-test.ebsi.eu/did-registry/v4/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw",
56
+ );
57
+ ```
58
+
59
+ returns
60
+
61
+ ```json
62
+ {
63
+ "network": "test",
64
+ "service": "did-registry",
65
+ "resource": "/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw"
66
+ }
67
+ ```
68
+
47
69
  ### Serialize
48
70
 
49
71
  The `serialize` function takes an object containing the URI components `network`, `service`, and `resource`. It returns a string representation of the EBSI URI.
package/dist/index.d.ts CHANGED
@@ -1,33 +1,43 @@
1
1
  export declare const SCHEME = "ebsi";
2
2
  export declare const PRODUCTION_NETWORK = "production";
3
3
  export declare const NETWORKS: readonly ["production", "preprod", "conformance", "pilot", "test"];
4
- export declare const HOSTS: {
5
- readonly conformance: "api-conformance.ebsi.eu";
6
- readonly pilot: "api-pilot.ebsi.";
7
- readonly preprod: "api-preprod.ebsi.";
8
- readonly production: "api.ebsi.";
9
- readonly test: "api-test.ebsi.eu";
4
+ export declare const NETWORKS_PATTERNS: {
5
+ readonly conformance: RegExp;
6
+ readonly pilot: RegExp;
7
+ readonly preprod: RegExp;
8
+ readonly production: RegExp;
9
+ readonly test: RegExp;
10
10
  };
11
- export interface EBSIURIComponents {
12
- network?: Network;
13
- resource?: string;
14
- service?: string;
11
+ export interface EBSIURIComponents<T extends string = string> {
12
+ network: Network;
13
+ resource: string;
14
+ service: ServiceName<T>;
15
+ }
16
+ export interface IFromUrlOptions<T extends string = string> {
17
+ readonly services?: SupportedServices<T>;
15
18
  }
16
- export interface IFromUrlOptions {
17
- services?: SupportedServices;
19
+ export interface IParseUrlOptions<T extends string = string> {
20
+ readonly services?: SupportedServices<T>;
18
21
  }
19
- export interface IToUrlOptions {
22
+ export interface IToUrlOptions<T extends string = string> {
20
23
  readonly hosts: Partial<Record<Network, string>>;
21
- readonly services: SupportedServices;
24
+ readonly services: SupportedServices<T>;
22
25
  }
23
26
  export type Network = (typeof NETWORKS)[number];
24
- export type SupportedServices = Record<string, string>;
25
- export declare function fromUrl(url: string, options?: IFromUrlOptions): string;
27
+ export type ServiceName<T extends string = string> = T;
28
+ export type ServiceVersion = `v${number}`;
29
+ export type SupportedServices<T extends string> = Record<ServiceName<T>, ServiceVersion>;
30
+ export declare function fromUrl<T extends string>(url: string, options?: IFromUrlOptions<T>): string;
26
31
  export declare function parse(uriString: string): {
27
32
  network: "production" | "preprod" | "conformance" | "pilot" | "test";
28
33
  resource: string;
29
34
  service: string;
30
35
  };
31
- export declare function serialize(components: EBSIURIComponents): string;
32
- export declare function toUrl(uriString: string, options: IToUrlOptions): string;
36
+ export declare function parseUrl<T extends string>(url: string, options?: IParseUrlOptions<T>): {
37
+ network: "production" | "preprod" | "conformance" | "pilot" | "test";
38
+ resource: string;
39
+ service: T;
40
+ };
41
+ export declare function serialize<T extends string>(components: EBSIURIComponents<T>): string;
42
+ export declare function toUrl<T extends string>(uriString: string, options: IToUrlOptions<T>): string;
33
43
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM,SAAS,CAAC;AAE7B,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAE/C,eAAO,MAAM,QAAQ,oEAMX,CAAC;AAEX,eAAO,MAAM,KAAK;;;;;;CAM0B,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAGD,MAAM,WAAW,eAAe;IAI9B,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAGD,MAAM,WAAW,aAAa;IAI5B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAKjD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;CACtC;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAavD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,UAsD7D;AAmBD,wBAAgB,KAAK,CAAC,SAAS,EAAE,MAAM;;;;EAgCtC;AAgBD,wBAAgB,SAAS,CAAC,UAAU,EAAE,iBAAiB,UAOtD;AAgBD,wBAAgB,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAmBvE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM,SAAS,CAAC;AAE7B,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAE/C,eAAO,MAAM,QAAQ,oEAMX,CAAC;AAEX,eAAO,MAAM,iBAAiB;;;;;;CAMc,CAAC;AAQ7C,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CACzB;AAGD,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAIxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;CAC1C;AAGD,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAIzD,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;CAC1C;AAGD,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAItD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAKjD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;CACzC;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,CAAC,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG,IAAI,MAAM,EAAE,CAAC;AAE1C,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM,IAAI,MAAM,CACtD,WAAW,CAAC,CAAC,CAAC,EACd,cAAc,CACf,CAAC;AAaF,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EACtC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,UAK7B;AAmBD,wBAAgB,KAAK,CAAC,SAAS,EAAE,MAAM;;;;EAgCtC;AAQD,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC;;;aA0DP,CAAC;EAExB;AAgBD,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC,UAO3E;AAgBD,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,EACpC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GACxB,MAAM,CAqBR"}
package/dist/index.js CHANGED
@@ -7,14 +7,40 @@ export const NETWORKS = [
7
7
  "pilot",
8
8
  "test",
9
9
  ];
10
- export const HOSTS = {
11
- conformance: "api-conformance.ebsi.eu",
12
- pilot: "api-pilot.ebsi.",
13
- preprod: "api-preprod.ebsi.",
14
- production: "api.ebsi.",
15
- test: "api-test.ebsi.eu",
10
+ export const NETWORKS_PATTERNS = {
11
+ conformance: /^api-conformance\.ebsi\.eu$/,
12
+ pilot: /^api-pilot\.ebsi\./,
13
+ preprod: /^api-preprod\.ebsi\./,
14
+ production: /^api\.ebsi\./,
15
+ test: /^api-test\.ebsi\.eu$/,
16
16
  };
17
17
  export function fromUrl(url, options) {
18
+ const components = parseUrl(url, options);
19
+ return serialize(components);
20
+ }
21
+ export function parse(uriString) {
22
+ const [scheme, firstComponent, secondComponent, ...rest] = uriString.split(":");
23
+ if (scheme !== SCHEME) {
24
+ throw new Error(`Invalid URI: scheme must be '${SCHEME}'`);
25
+ }
26
+ const network = isNetwork(firstComponent)
27
+ ? firstComponent
28
+ : PRODUCTION_NETWORK;
29
+ const service = isNetwork(firstComponent) ? secondComponent : firstComponent;
30
+ if (!service || service.startsWith("/")) {
31
+ throw new Error(`No service found in EBSI URI '${uriString}'`);
32
+ }
33
+ const resource = (isNetwork(firstComponent) ? rest : [secondComponent, ...rest]).join(":");
34
+ if (!resource) {
35
+ throw new Error(`No resource found in EBSI URI '${uriString}'`);
36
+ }
37
+ return {
38
+ network,
39
+ resource,
40
+ service,
41
+ };
42
+ }
43
+ export function parseUrl(url, options) {
18
44
  let urlObject;
19
45
  try {
20
46
  urlObject = new URL(url);
@@ -23,55 +49,37 @@ export function fromUrl(url, options) {
23
49
  throw new Error("Invalid URL");
24
50
  }
25
51
  const { hash, host, pathname, search } = urlObject;
26
- const network = Object.keys(HOSTS).find((n) => host.includes(HOSTS[n]));
52
+ const network = NETWORKS.find((n) => NETWORKS_PATTERNS[n].test(host));
27
53
  if (!network) {
28
- throw new Error(`No network configured for host ${host}`);
54
+ throw new Error(`Host ${host} doesn't match any EBSI network`);
29
55
  }
30
56
  const [service, serviceVersion, ...rest] = pathname
31
57
  .split("/")
32
58
  .filter(Boolean);
33
- const resource = rest.join("/");
34
59
  if (!service) {
35
60
  throw new Error(`No service found in EBSI URI ${url}`);
36
61
  }
37
62
  if (!serviceVersion) {
38
63
  throw new Error(`No service version found in EBSI URI ${url}`);
39
64
  }
65
+ if (!isServiceVersion(serviceVersion)) {
66
+ throw new Error(`'${serviceVersion}' is not a valid service version`);
67
+ }
40
68
  if (options?.services) {
41
- const supportedServiceVersion = options.services[service];
42
- if (!supportedServiceVersion) {
69
+ const services = options.services;
70
+ if (!isSupportedService(service, services)) {
43
71
  throw new Error(`Unsupported service '${service}'`);
44
72
  }
73
+ const supportedServiceVersion = services[service];
45
74
  if (serviceVersion !== supportedServiceVersion) {
46
75
  throw new Error(`Unsupported version '${serviceVersion}' of service '${service}'`);
47
76
  }
48
77
  }
49
- return serialize({
50
- network,
51
- resource: `/${resource}${search}${hash}`,
52
- service,
53
- });
54
- }
55
- export function parse(uriString) {
56
- const [scheme, firstComponent, secondComponent, ...rest] = uriString.split(":");
57
- if (scheme !== SCHEME) {
58
- throw new Error("Invalid URI: scheme must be 'ebsi'");
59
- }
60
- const network = isNetwork(firstComponent)
61
- ? firstComponent
62
- : PRODUCTION_NETWORK;
63
- const service = isNetwork(firstComponent) ? secondComponent : firstComponent;
64
- if (!service || service.startsWith("/")) {
65
- throw new Error(`No service found in EBSI URI '${uriString}'`);
66
- }
67
- const resource = (isNetwork(firstComponent) ? rest : [secondComponent, ...rest]).join(":");
68
- if (!resource) {
69
- throw new Error(`No resource found in EBSI URI '${uriString}'`);
70
- }
78
+ const resource = `/${rest.join("/")}${search}${hash}`;
71
79
  return {
72
80
  network,
73
81
  resource,
74
- service,
82
+ service: service,
75
83
  };
76
84
  }
77
85
  export function serialize(components) {
@@ -85,13 +93,20 @@ export function toUrl(uriString, options) {
85
93
  if (!host) {
86
94
  throw new Error(`No host for network ${network}`);
87
95
  }
88
- const serviceVersion = options.services[service];
89
- if (serviceVersion === undefined) {
90
- throw new Error(`Unsupported service: ${service}`);
96
+ const services = options.services;
97
+ if (!isSupportedService(service, services)) {
98
+ throw new Error(`Unsupported service '${service}'`);
91
99
  }
100
+ const serviceVersion = services[service];
92
101
  return `https://${host}/${service}/${serviceVersion}${resource}`;
93
102
  }
94
103
  function isNetwork(network) {
95
104
  return network !== undefined && NETWORKS.includes(network);
96
105
  }
106
+ function isServiceVersion(serviceVersion) {
107
+ return typeof serviceVersion === "string" && /v\d*/.test(serviceVersion);
108
+ }
109
+ function isSupportedService(service, supportedServices) {
110
+ return service !== undefined && service in supportedServices;
111
+ }
97
112
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cef-ebsi/ebsi-uri",
3
- "version": "0.1.1-alpha.0",
3
+ "version": "0.2.0-alpha.0",
4
4
  "description": "EBSI URIs parser and serializer",
5
5
  "license": "EUPL-1.2",
6
6
  "publishConfig": {
@@ -30,5 +30,5 @@
30
30
  "test:e2e": "echo \"Info: there's no e2e tests for @cef-ebsi/ebsi-uri\" && exit 0",
31
31
  "test:ci": "yarn test:unit --coverage --reporter=default"
32
32
  },
33
- "gitHead": "4900bd20f6d6ee78709b0f7f04a5f4e4f11e0b67"
33
+ "gitHead": "da5d07e18f31b1e569520c8c58c28d6601a24624"
34
34
  }
package/src/index.ts CHANGED
@@ -10,30 +10,44 @@ export const NETWORKS = [
10
10
  "test",
11
11
  ] as const;
12
12
 
13
- export const HOSTS = {
14
- conformance: "api-conformance.ebsi.eu",
15
- pilot: "api-pilot.ebsi.",
16
- preprod: "api-preprod.ebsi.",
17
- production: "api.ebsi.",
18
- test: "api-test.ebsi.eu",
19
- } as const satisfies Record<Network, string>;
20
-
21
- export interface EBSIURIComponents {
22
- network?: Network;
23
- resource?: string;
24
- service?: string;
13
+ export const NETWORKS_PATTERNS = {
14
+ conformance: /^api-conformance\.ebsi\.eu$/,
15
+ pilot: /^api-pilot\.ebsi\./,
16
+ preprod: /^api-preprod\.ebsi\./,
17
+ production: /^api\.ebsi\./,
18
+ test: /^api-test\.ebsi\.eu$/,
19
+ } as const satisfies Record<Network, RegExp>;
20
+
21
+ /**
22
+ * Components of an EBSI URI:
23
+ * - a network, e.g. "pilot"
24
+ * - a service, e.g. "trusted-issuers-registry"
25
+ * - a resource path, e.g. "/issuers/did:ebsi:zZeKyEJfUTGwajhNyNX928z?x=y&z=w#abc
26
+ */
27
+ export interface EBSIURIComponents<T extends string = string> {
28
+ network: Network;
29
+ resource: string;
30
+ service: ServiceName<T>;
25
31
  }
26
32
 
27
33
  /** Options for the `fromUrl()` function */
28
- export interface IFromUrlOptions {
34
+ export interface IFromUrlOptions<T extends string = string> {
35
+ /**
36
+ * Map of supported services and their versions
37
+ */
38
+ readonly services?: SupportedServices<T>;
39
+ }
40
+
41
+ /** Options for the `parseUrl()` function */
42
+ export interface IParseUrlOptions<T extends string = string> {
29
43
  /**
30
44
  * Map of supported services and their versions
31
45
  */
32
- services?: SupportedServices;
46
+ readonly services?: SupportedServices<T>;
33
47
  }
34
48
 
35
49
  /** Options for the `toUrl()` function */
36
- export interface IToUrlOptions {
50
+ export interface IToUrlOptions<T extends string = string> {
37
51
  /**
38
52
  * Map of supported networks and their hosts
39
53
  */
@@ -42,12 +56,19 @@ export interface IToUrlOptions {
42
56
  /**
43
57
  * Map of supported services and their versions
44
58
  */
45
- readonly services: SupportedServices;
59
+ readonly services: SupportedServices<T>;
46
60
  }
47
61
 
48
62
  export type Network = (typeof NETWORKS)[number];
49
63
 
50
- export type SupportedServices = Record<string, string>;
64
+ export type ServiceName<T extends string = string> = T;
65
+
66
+ export type ServiceVersion = `v${number}`;
67
+
68
+ export type SupportedServices<T extends string> = Record<
69
+ ServiceName<T>,
70
+ ServiceVersion
71
+ >;
51
72
 
52
73
  /**
53
74
  * Transform a URL into an EBSI URI.
@@ -60,60 +81,13 @@ export type SupportedServices = Record<string, string>;
60
81
  * @param options - Supported services
61
82
  * @returns EBSI URI
62
83
  */
63
- export function fromUrl(url: string, options?: IFromUrlOptions) {
64
- // Parse URL
65
- let urlObject: URL;
66
- try {
67
- urlObject = new URL(url);
68
- } catch {
69
- throw new Error("Invalid URL");
70
- }
71
-
72
- const { hash, host, pathname, search } = urlObject;
84
+ export function fromUrl<T extends string>(
85
+ url: string,
86
+ options?: IFromUrlOptions<T>,
87
+ ) {
88
+ const components = parseUrl(url, options);
73
89
 
74
- // Identify network
75
- const network = (Object.keys(HOSTS) as (keyof typeof HOSTS)[]).find((n) =>
76
- host.includes(HOSTS[n]),
77
- );
78
-
79
- if (!network) {
80
- throw new Error(`No network configured for host ${host}`);
81
- }
82
-
83
- // Extract service and resource from pathname: /<service>/<serviceVersion>/<resource>
84
- const [service, serviceVersion, ...rest] = pathname
85
- .split("/")
86
- .filter(Boolean); // Remove empty path segments
87
- const resource = rest.join("/");
88
-
89
- if (!service) {
90
- throw new Error(`No service found in EBSI URI ${url}`);
91
- }
92
-
93
- if (!serviceVersion) {
94
- throw new Error(`No service version found in EBSI URI ${url}`);
95
- }
96
-
97
- // Check if the service and its version are supported
98
- if (options?.services) {
99
- const supportedServiceVersion = options.services[service];
100
-
101
- if (!supportedServiceVersion) {
102
- throw new Error(`Unsupported service '${service}'`);
103
- }
104
-
105
- if (serviceVersion !== supportedServiceVersion) {
106
- throw new Error(
107
- `Unsupported version '${serviceVersion}' of service '${service}'`,
108
- );
109
- }
110
- }
111
-
112
- return serialize({
113
- network,
114
- resource: `/${resource}${search}${hash}`,
115
- service,
116
- });
90
+ return serialize(components);
117
91
  }
118
92
 
119
93
  /**
@@ -138,10 +112,10 @@ export function parse(uriString: string) {
138
112
  uriString.split(":");
139
113
 
140
114
  if (scheme !== SCHEME) {
141
- throw new Error("Invalid URI: scheme must be 'ebsi'");
115
+ throw new Error(`Invalid URI: scheme must be '${SCHEME}'`);
142
116
  }
143
117
 
144
- const network: Network = isNetwork(firstComponent)
118
+ const network = isNetwork(firstComponent)
145
119
  ? firstComponent
146
120
  : PRODUCTION_NETWORK;
147
121
 
@@ -164,7 +138,77 @@ export function parse(uriString: string) {
164
138
  network,
165
139
  resource,
166
140
  service,
167
- } satisfies EBSIURIComponents;
141
+ } satisfies EBSIURIComponents<string>;
142
+ }
143
+
144
+ /**
145
+ * Extract EBSI URI components from URL.
146
+ * @param url - The URL to parse.
147
+ * @param options - Supported services
148
+ * @returns EBSI URI components
149
+ */
150
+ export function parseUrl<T extends string>(
151
+ url: string,
152
+ options?: IParseUrlOptions<T>,
153
+ ) {
154
+ // Parse URL
155
+ let urlObject: URL;
156
+ try {
157
+ urlObject = new URL(url);
158
+ } catch {
159
+ throw new Error("Invalid URL");
160
+ }
161
+
162
+ const { hash, host, pathname, search } = urlObject;
163
+
164
+ // Identify network
165
+ const network = NETWORKS.find((n) => NETWORKS_PATTERNS[n].test(host));
166
+
167
+ if (!network) {
168
+ throw new Error(`Host ${host} doesn't match any EBSI network`);
169
+ }
170
+
171
+ // Extract service and resource from pathname: /<service>/<serviceVersion>/<resource>
172
+ const [service, serviceVersion, ...rest] = pathname
173
+ .split("/")
174
+ .filter(Boolean); // Remove empty path segments
175
+
176
+ if (!service) {
177
+ throw new Error(`No service found in EBSI URI ${url}`);
178
+ }
179
+
180
+ if (!serviceVersion) {
181
+ throw new Error(`No service version found in EBSI URI ${url}`);
182
+ }
183
+
184
+ if (!isServiceVersion(serviceVersion)) {
185
+ throw new Error(`'${serviceVersion}' is not a valid service version`);
186
+ }
187
+
188
+ // Check if the service and its version are supported
189
+ if (options?.services) {
190
+ const services = options.services;
191
+
192
+ if (!isSupportedService(service, services)) {
193
+ throw new Error(`Unsupported service '${service}'`);
194
+ }
195
+
196
+ const supportedServiceVersion = services[service];
197
+
198
+ if (serviceVersion !== supportedServiceVersion) {
199
+ throw new Error(
200
+ `Unsupported version '${serviceVersion}' of service '${service}'`,
201
+ );
202
+ }
203
+ }
204
+
205
+ const resource = `/${rest.join("/")}${search}${hash}`;
206
+
207
+ return {
208
+ network,
209
+ resource,
210
+ service: service as T,
211
+ } satisfies EBSIURIComponents<T>;
168
212
  }
169
213
 
170
214
  /**
@@ -181,7 +225,7 @@ export function parse(uriString: string) {
181
225
  * @param components - EBSI URI components
182
226
  * @returns EBSI URI
183
227
  */
184
- export function serialize(components: EBSIURIComponents) {
228
+ export function serialize<T extends string>(components: EBSIURIComponents<T>) {
185
229
  const { network, resource, service } = components;
186
230
 
187
231
  const networkName = network === PRODUCTION_NETWORK ? "" : `${network}:`;
@@ -204,7 +248,10 @@ export function serialize(components: EBSIURIComponents) {
204
248
  * @param options - Supported services and hosts
205
249
  * @returns URL
206
250
  */
207
- export function toUrl(uriString: string, options: IToUrlOptions): string {
251
+ export function toUrl<T extends string>(
252
+ uriString: string,
253
+ options: IToUrlOptions<T>,
254
+ ): string {
208
255
  const { network, resource, service } = parse(uriString);
209
256
 
210
257
  // Replace host
@@ -214,13 +261,15 @@ export function toUrl(uriString: string, options: IToUrlOptions): string {
214
261
  throw new Error(`No host for network ${network}`);
215
262
  }
216
263
 
217
- // Get service version
218
- const serviceVersion = options.services[service];
264
+ const services = options.services;
219
265
 
220
- if (serviceVersion === undefined) {
221
- throw new Error(`Unsupported service: ${service}`);
266
+ if (!isSupportedService(service, services)) {
267
+ throw new Error(`Unsupported service '${service}'`);
222
268
  }
223
269
 
270
+ // Get service version
271
+ const serviceVersion = services[service];
272
+
224
273
  // Return URL
225
274
  return `https://${host}/${service}/${serviceVersion}${resource}`;
226
275
  }
@@ -228,8 +277,32 @@ export function toUrl(uriString: string, options: IToUrlOptions): string {
228
277
  /**
229
278
  * Typeguard that checks if the given string is a supported network.
230
279
  * @param network - String to check
231
- * @returns - network is Network
280
+ * @returns True if network is a valid network name
232
281
  */
233
282
  function isNetwork(network: string | undefined): network is Network {
234
283
  return network !== undefined && NETWORKS.includes(network as Network);
235
284
  }
285
+
286
+ /**
287
+ * Typeguard that checks if the given string is a supported service version.
288
+ * @param serviceVersion - String to check
289
+ * @returns True if serviceVersion is a valid service version
290
+ */
291
+ function isServiceVersion(
292
+ serviceVersion: string | undefined,
293
+ ): serviceVersion is ServiceVersion {
294
+ return typeof serviceVersion === "string" && /v\d*/.test(serviceVersion);
295
+ }
296
+
297
+ /**
298
+ * Typeguard that checks if the given string is a supported service.
299
+ * @param service - String to check
300
+ * @param supportedServices - Supported services
301
+ * @returns True if service is a supported service
302
+ */
303
+ function isSupportedService<T extends string>(
304
+ service: string | undefined,
305
+ supportedServices: SupportedServices<T>,
306
+ ): service is T {
307
+ return service !== undefined && service in supportedServices;
308
+ }