@cef-ebsi/ebsi-uri 0.1.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,31 +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 test: "api-test.ebsi.eu";
6
- readonly pilot: "api-pilot.ebsi.";
7
- readonly conformance: "api-conformance.ebsi.eu";
8
- readonly preprod: "api-preprod.ebsi.";
9
- readonly production: "api.ebsi.";
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<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>;
18
+ }
19
+ export interface IParseUrlOptions<T extends string = string> {
20
+ readonly services?: SupportedServices<T>;
21
+ }
22
+ export interface IToUrlOptions<T extends string = string> {
23
+ readonly hosts: Partial<Record<Network, string>>;
24
+ readonly services: SupportedServices<T>;
25
+ }
11
26
  export type Network = (typeof NETWORKS)[number];
12
- export type EBSIURIComponents = {
13
- network?: Network;
14
- service?: string;
15
- resource?: string;
16
- };
17
- export type SupportedServices = Record<string, 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;
18
31
  export declare function parse(uriString: string): {
19
32
  network: "production" | "preprod" | "conformance" | "pilot" | "test";
33
+ resource: string;
20
34
  service: string;
35
+ };
36
+ export declare function parseUrl<T extends string>(url: string, options?: IParseUrlOptions<T>): {
37
+ network: "production" | "preprod" | "conformance" | "pilot" | "test";
21
38
  resource: string;
39
+ service: T;
22
40
  };
23
- export declare function serialize(components: EBSIURIComponents): string;
24
- export declare function toUrl(uriString: string, options: {
25
- readonly services: SupportedServices;
26
- readonly hosts: Partial<Record<Network, string>>;
27
- }): string;
28
- export declare function fromUrl(url: string, options?: {
29
- services?: SupportedServices;
30
- }): string;
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;
31
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,MAAM,OAAO,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAuBvD,wBAAgB,KAAK,CAAC,SAAS,EAAE,MAAM;;;;EAgCtC;AAgBD,wBAAgB,SAAS,CAAC,UAAU,EAAE,iBAAiB,UAOtD;AAgBD,wBAAgB,KAAK,CACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAIP,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IAKrC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;CAClD,GACA,MAAM,CAmBR;AAaD,wBAAgB,OAAO,CACrB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAIR,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B,UAuDF"}
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,20 +7,21 @@ export const NETWORKS = [
7
7
  "pilot",
8
8
  "test",
9
9
  ];
10
- export const HOSTS = {
11
- test: "api-test.ebsi.eu",
12
- pilot: "api-pilot.ebsi.",
13
- conformance: "api-conformance.ebsi.eu",
14
- preprod: "api-preprod.ebsi.",
15
- production: "api.ebsi.",
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
- function isNetwork(network) {
18
- return network !== undefined && NETWORKS.includes(network);
17
+ export function fromUrl(url, options) {
18
+ const components = parseUrl(url, options);
19
+ return serialize(components);
19
20
  }
20
21
  export function parse(uriString) {
21
22
  const [scheme, firstComponent, secondComponent, ...rest] = uriString.split(":");
22
23
  if (scheme !== SCHEME) {
23
- throw new Error("Invalid URI: scheme must be 'ebsi'");
24
+ throw new Error(`Invalid URI: scheme must be '${SCHEME}'`);
24
25
  }
25
26
  const network = isNetwork(firstComponent)
26
27
  ? firstComponent
@@ -35,28 +36,11 @@ export function parse(uriString) {
35
36
  }
36
37
  return {
37
38
  network,
38
- service,
39
39
  resource,
40
+ service,
40
41
  };
41
42
  }
42
- export function serialize(components) {
43
- const { network, service, resource } = components;
44
- const networkName = network === PRODUCTION_NETWORK ? "" : `${network}:`;
45
- return `${SCHEME}:${networkName}${service}:${resource}`;
46
- }
47
- export function toUrl(uriString, options) {
48
- const { network, service, resource } = parse(uriString);
49
- const host = options.hosts[network];
50
- if (!host) {
51
- throw new Error(`No host for network ${network}`);
52
- }
53
- const serviceVersion = options.services[service];
54
- if (serviceVersion === undefined) {
55
- throw new Error(`Unsupported service: ${service}`);
56
- }
57
- return `https://${host}/${service}/${serviceVersion}${resource}`;
58
- }
59
- export function fromUrl(url, options) {
43
+ export function parseUrl(url, options) {
60
44
  let urlObject;
61
45
  try {
62
46
  urlObject = new URL(url);
@@ -64,34 +48,65 @@ export function fromUrl(url, options) {
64
48
  catch {
65
49
  throw new Error("Invalid URL");
66
50
  }
67
- const { host, pathname, search, hash } = urlObject;
68
- const network = Object.keys(HOSTS).find((n) => host.includes(HOSTS[n]));
51
+ const { hash, host, pathname, search } = urlObject;
52
+ const network = NETWORKS.find((n) => NETWORKS_PATTERNS[n].test(host));
69
53
  if (!network) {
70
- throw new Error(`No network configured for host ${host}`);
54
+ throw new Error(`Host ${host} doesn't match any EBSI network`);
71
55
  }
72
56
  const [service, serviceVersion, ...rest] = pathname
73
57
  .split("/")
74
58
  .filter(Boolean);
75
- const resource = rest.join("/");
76
59
  if (!service) {
77
60
  throw new Error(`No service found in EBSI URI ${url}`);
78
61
  }
79
62
  if (!serviceVersion) {
80
63
  throw new Error(`No service version found in EBSI URI ${url}`);
81
64
  }
65
+ if (!isServiceVersion(serviceVersion)) {
66
+ throw new Error(`'${serviceVersion}' is not a valid service version`);
67
+ }
82
68
  if (options?.services) {
83
- const supportedServiceVersion = options.services[service];
84
- if (!supportedServiceVersion) {
69
+ const services = options.services;
70
+ if (!isSupportedService(service, services)) {
85
71
  throw new Error(`Unsupported service '${service}'`);
86
72
  }
73
+ const supportedServiceVersion = services[service];
87
74
  if (serviceVersion !== supportedServiceVersion) {
88
75
  throw new Error(`Unsupported version '${serviceVersion}' of service '${service}'`);
89
76
  }
90
77
  }
91
- return serialize({
78
+ const resource = `/${rest.join("/")}${search}${hash}`;
79
+ return {
92
80
  network,
93
- service,
94
- resource: `/${resource}${search}${hash}`,
95
- });
81
+ resource,
82
+ service: service,
83
+ };
84
+ }
85
+ export function serialize(components) {
86
+ const { network, resource, service } = components;
87
+ const networkName = network === PRODUCTION_NETWORK ? "" : `${network}:`;
88
+ return `${SCHEME}:${networkName}${service}:${resource}`;
89
+ }
90
+ export function toUrl(uriString, options) {
91
+ const { network, resource, service } = parse(uriString);
92
+ const host = options.hosts[network];
93
+ if (!host) {
94
+ throw new Error(`No host for network ${network}`);
95
+ }
96
+ const services = options.services;
97
+ if (!isSupportedService(service, services)) {
98
+ throw new Error(`Unsupported service '${service}'`);
99
+ }
100
+ const serviceVersion = services[service];
101
+ return `https://${host}/${service}/${serviceVersion}${resource}`;
102
+ }
103
+ function isNetwork(network) {
104
+ return network !== undefined && NETWORKS.includes(network);
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;
96
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":"AAAA,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC;AAE7B,MAAM,CAAC,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE/C,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,kBAAkB;IAClB,SAAS;IACT,aAAa;IACb,OAAO;IACP,MAAM;CACE,CAAC;AAEX,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,iBAAiB;IACxB,WAAW,EAAE,yBAAyB;IACtC,OAAO,EAAE,mBAAmB;IAC5B,UAAU,EAAE,WAAW;CACmB,CAAC;AAY7C,SAAS,SAAS,CAAC,OAA2B;IAC5C,OAAO,OAAO,KAAK,SAAS,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAkB,CAAC,CAAC;AACxE,CAAC;AAmBD,MAAM,UAAU,KAAK,CAAC,SAAiB;IACrC,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC,GACtD,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEvB,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,OAAO,GAAY,SAAS,CAAC,cAAc,CAAC;QAChD,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,kBAAkB,CAAC;IAEvB,MAAM,OAAO,GAAG,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,cAAc,CAAC;IAE7E,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,iCAAiC,SAAS,GAAG,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,QAAQ,GAAG,CACf,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,eAAe,EAAE,GAAG,IAAI,CAAC,CAC9D,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEZ,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,kCAAkC,SAAS,GAAG,CAAC,CAAC;IAClE,CAAC;IAGD,OAAO;QACL,OAAO;QACP,OAAO;QACP,QAAQ;KACmB,CAAC;AAChC,CAAC;AAgBD,MAAM,UAAU,SAAS,CAAC,UAA6B;IACrD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,CAAC;IAElD,MAAM,WAAW,GAAG,OAAO,KAAK,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC;IAGxE,OAAO,GAAG,MAAM,IAAI,WAAW,GAAG,OAAO,IAAI,QAAQ,EAAE,CAAC;AAC1D,CAAC;AAgBD,MAAM,UAAU,KAAK,CACnB,SAAiB,EACjB,OAUC;IAED,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IAGxD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAEpC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,uBAAuB,OAAO,EAAE,CAAC,CAAC;IACpD,CAAC;IAGD,MAAM,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAEjD,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;IACrD,CAAC;IAGD,OAAO,WAAW,IAAI,IAAI,OAAO,IAAI,cAAc,GAAG,QAAQ,EAAE,CAAC;AACnE,CAAC;AAaD,MAAM,UAAU,OAAO,CACrB,GAAW,EACX,OAKC;IAGD,IAAI,SAAc,CAAC;IACnB,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC;IAGnD,MAAM,OAAO,GAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAA4B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CACxE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CACxB,CAAC;IAEF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAGD,MAAM,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ;SAChD,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,OAAO,CAAC,CAAC;IACnB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEhC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,gCAAgC,GAAG,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,wCAAwC,GAAG,EAAE,CAAC,CAAC;IACjE,CAAC;IAGD,IAAI,OAAO,EAAE,QAAQ,EAAE,CAAC;QACtB,MAAM,uBAAuB,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAE1D,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,wBAAwB,OAAO,GAAG,CAAC,CAAC;QACtD,CAAC;QAED,IAAI,cAAc,KAAK,uBAAuB,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CACb,wBAAwB,cAAc,iBAAiB,OAAO,GAAG,CAClE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;QACf,OAAO;QACP,OAAO;QACP,QAAQ,EAAE,IAAI,QAAQ,GAAG,MAAM,GAAG,IAAI,EAAE;KACzC,CAAC,CAAC;AACL,CAAC"}
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.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": "4a9d0f7280c4a4d60876bd574cffe6291e43f3d0"
33
+ "gitHead": "da5d07e18f31b1e569520c8c58c28d6601a24624"
34
34
  }
package/src/index.ts CHANGED
@@ -10,26 +10,84 @@ export const NETWORKS = [
10
10
  "test",
11
11
  ] as const;
12
12
 
13
- export const HOSTS = {
14
- test: "api-test.ebsi.eu",
15
- pilot: "api-pilot.ebsi.",
16
- conformance: "api-conformance.ebsi.eu",
17
- preprod: "api-preprod.ebsi.",
18
- production: "api.ebsi.",
19
- } as const satisfies Record<Network, 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>;
31
+ }
32
+
33
+ /** Options for the `fromUrl()` function */
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> {
43
+ /**
44
+ * Map of supported services and their versions
45
+ */
46
+ readonly services?: SupportedServices<T>;
47
+ }
48
+
49
+ /** Options for the `toUrl()` function */
50
+ export interface IToUrlOptions<T extends string = string> {
51
+ /**
52
+ * Map of supported networks and their hosts
53
+ */
54
+ readonly hosts: Partial<Record<Network, string>>;
55
+
56
+ /**
57
+ * Map of supported services and their versions
58
+ */
59
+ readonly services: SupportedServices<T>;
60
+ }
20
61
 
21
62
  export type Network = (typeof NETWORKS)[number];
22
63
 
23
- export type EBSIURIComponents = {
24
- network?: Network;
25
- service?: string;
26
- resource?: string;
27
- };
64
+ export type ServiceName<T extends string = string> = T;
28
65
 
29
- export type SupportedServices = Record<string, string>;
66
+ export type ServiceVersion = `v${number}`;
30
67
 
31
- function isNetwork(network: string | undefined): network is Network {
32
- return network !== undefined && NETWORKS.includes(network as Network);
68
+ export type SupportedServices<T extends string> = Record<
69
+ ServiceName<T>,
70
+ ServiceVersion
71
+ >;
72
+
73
+ /**
74
+ * Transform a URL into an EBSI URI.
75
+ * @example
76
+ * ```js
77
+ * fromUrl("https://api-test.ebsi.eu/did-registry/v4/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw")
78
+ * ```
79
+ * returns `ebsi:test:did-registry:/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw`
80
+ * @param url - URL to transform
81
+ * @param options - Supported services
82
+ * @returns EBSI URI
83
+ */
84
+ export function fromUrl<T extends string>(
85
+ url: string,
86
+ options?: IFromUrlOptions<T>,
87
+ ) {
88
+ const components = parseUrl(url, options);
89
+
90
+ return serialize(components);
33
91
  }
34
92
 
35
93
  /**
@@ -54,10 +112,10 @@ export function parse(uriString: string) {
54
112
  uriString.split(":");
55
113
 
56
114
  if (scheme !== SCHEME) {
57
- throw new Error("Invalid URI: scheme must be 'ebsi'");
115
+ throw new Error(`Invalid URI: scheme must be '${SCHEME}'`);
58
116
  }
59
117
 
60
- const network: Network = isNetwork(firstComponent)
118
+ const network = isNetwork(firstComponent)
61
119
  ? firstComponent
62
120
  : PRODUCTION_NETWORK;
63
121
 
@@ -78,9 +136,79 @@ export function parse(uriString: string) {
78
136
  // Return EBSI URI components
79
137
  return {
80
138
  network,
139
+ resource,
81
140
  service,
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,
82
209
  resource,
83
- } satisfies EBSIURIComponents;
210
+ service: service as T,
211
+ } satisfies EBSIURIComponents<T>;
84
212
  }
85
213
 
86
214
  /**
@@ -97,8 +225,8 @@ export function parse(uriString: string) {
97
225
  * @param components - EBSI URI components
98
226
  * @returns EBSI URI
99
227
  */
100
- export function serialize(components: EBSIURIComponents) {
101
- const { network, service, resource } = components;
228
+ export function serialize<T extends string>(components: EBSIURIComponents<T>) {
229
+ const { network, resource, service } = components;
102
230
 
103
231
  const networkName = network === PRODUCTION_NETWORK ? "" : `${network}:`;
104
232
 
@@ -120,21 +248,11 @@ export function serialize(components: EBSIURIComponents) {
120
248
  * @param options - Supported services and hosts
121
249
  * @returns URL
122
250
  */
123
- export function toUrl(
251
+ export function toUrl<T extends string>(
124
252
  uriString: string,
125
- options: {
126
- /**
127
- * Map of supported services and their versions
128
- */
129
- readonly services: SupportedServices;
130
-
131
- /**
132
- * Map of supported networks and their hosts
133
- */
134
- readonly hosts: Partial<Record<Network, string>>;
135
- },
253
+ options: IToUrlOptions<T>,
136
254
  ): string {
137
- const { network, service, resource } = parse(uriString);
255
+ const { network, resource, service } = parse(uriString);
138
256
 
139
257
  // Replace host
140
258
  const host = options.hosts[network];
@@ -143,88 +261,48 @@ export function toUrl(
143
261
  throw new Error(`No host for network ${network}`);
144
262
  }
145
263
 
146
- // Get service version
147
- const serviceVersion = options.services[service];
264
+ const services = options.services;
148
265
 
149
- if (serviceVersion === undefined) {
150
- throw new Error(`Unsupported service: ${service}`);
266
+ if (!isSupportedService(service, services)) {
267
+ throw new Error(`Unsupported service '${service}'`);
151
268
  }
152
269
 
270
+ // Get service version
271
+ const serviceVersion = services[service];
272
+
153
273
  // Return URL
154
274
  return `https://${host}/${service}/${serviceVersion}${resource}`;
155
275
  }
156
276
 
157
277
  /**
158
- * Transform a URL into an EBSI URI.
159
- * @example
160
- * ```js
161
- * fromUrl("https://api-test.ebsi.eu/did-registry/v4/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw")
162
- * ```
163
- * returns `ebsi:test:did-registry:/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw`
164
- * @param url - URL to transform
165
- * @param options - Supported services
166
- * @returns EBSI URI
278
+ * Typeguard that checks if the given string is a supported network.
279
+ * @param network - String to check
280
+ * @returns True if network is a valid network name
167
281
  */
168
- export function fromUrl(
169
- url: string,
170
- options?: {
171
- /**
172
- * Map of supported services and their versions
173
- */
174
- services?: SupportedServices;
175
- },
176
- ) {
177
- // Parse URL
178
- let urlObject: URL;
179
- try {
180
- urlObject = new URL(url);
181
- } catch {
182
- throw new Error("Invalid URL");
183
- }
184
-
185
- const { host, pathname, search, hash } = urlObject;
186
-
187
- // Identify network
188
- const network = (Object.keys(HOSTS) as (keyof typeof HOSTS)[]).find((n) =>
189
- host.includes(HOSTS[n]),
190
- );
191
-
192
- if (!network) {
193
- throw new Error(`No network configured for host ${host}`);
194
- }
195
-
196
- // Extract service and resource from pathname: /<service>/<serviceVersion>/<resource>
197
- const [service, serviceVersion, ...rest] = pathname
198
- .split("/")
199
- .filter(Boolean); // Remove empty path segments
200
- const resource = rest.join("/");
201
-
202
- if (!service) {
203
- throw new Error(`No service found in EBSI URI ${url}`);
204
- }
205
-
206
- if (!serviceVersion) {
207
- throw new Error(`No service version found in EBSI URI ${url}`);
208
- }
209
-
210
- // Check if the service and its version are supported
211
- if (options?.services) {
212
- const supportedServiceVersion = options.services[service];
213
-
214
- if (!supportedServiceVersion) {
215
- throw new Error(`Unsupported service '${service}'`);
216
- }
282
+ function isNetwork(network: string | undefined): network is Network {
283
+ return network !== undefined && NETWORKS.includes(network as Network);
284
+ }
217
285
 
218
- if (serviceVersion !== supportedServiceVersion) {
219
- throw new Error(
220
- `Unsupported version '${serviceVersion}' of service '${service}'`,
221
- );
222
- }
223
- }
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
+ }
224
296
 
225
- return serialize({
226
- network,
227
- service,
228
- resource: `/${resource}${search}${hash}`,
229
- });
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;
230
308
  }