geoserver-node-client 0.0.6 → 1.1.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/src/style.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import fetch from 'node-fetch';
2
2
  import WorkspaceClient from './workspace.js';
3
+ import { getGeoServerResponseText, GeoServerResponseError } from './util/geoserver.js';
4
+ import AboutClient from './about.js'
3
5
 
4
6
  /**
5
7
  * Client for GeoServer styles
@@ -11,41 +13,34 @@ export default class StyleClient {
11
13
  * Creates a GeoServer REST StyleClient instance.
12
14
  *
13
15
  * @param {String} url The URL of the GeoServer REST API endpoint
14
- * @param {String} user The user for the GeoServer REST API
15
- * @param {String} password The password for the GeoServer REST API
16
+ * @param {String} auth The Basic Authentication string
16
17
  */
17
- constructor (url, user, password) {
18
- this.url = url.endsWith('/') ? url : url + '/';
19
- this.user = user;
20
- this.password = password;
18
+ constructor (url, auth) {
19
+ this.url = url;
20
+ this.auth = auth;
21
21
  }
22
22
 
23
23
  /**
24
24
  * Returns all default styles.
25
25
  *
26
- * @returns {Object|Boolean} An object with the default styles or 'false'
26
+ * @throws Error if request fails
27
+ *
28
+ * @returns {Object} An object with the default styles
27
29
  */
28
30
  async getDefaults () {
29
- try {
30
- const auth =
31
- Buffer.from(this.user + ':' + this.password).toString('base64');
32
- const response = await fetch(this.url + 'styles.json', {
33
- credentials: 'include',
34
- method: 'GET',
35
- headers: {
36
- Authorization: 'Basic ' + auth
37
- }
38
- });
39
-
40
- if (response.status === 200) {
41
- return await response.json();
42
- } else {
43
- console.warn(await response.text());
44
- return false;
31
+ const response = await fetch(this.url + 'styles.json', {
32
+ credentials: 'include',
33
+ method: 'GET',
34
+ headers: {
35
+ Authorization: this.auth
45
36
  }
46
- } catch (error) {
47
- return false;
37
+ });
38
+
39
+ if (!response.ok) {
40
+ const geoServerResponse = await getGeoServerResponseText(response);
41
+ throw new GeoServerResponseError(null, geoServerResponse);
48
42
  }
43
+ return response.json();
49
44
  }
50
45
 
51
46
  /**
@@ -53,184 +48,242 @@ export default class StyleClient {
53
48
  *
54
49
  * @param {String} workspace Workspace name to get styles for
55
50
  *
56
- * @returns {Object|Boolean} An object with all styles or 'false'
51
+ * @throws Error if request fails
52
+ *
53
+ * @returns {Object} An object with all styles
57
54
  */
58
55
  async getInWorkspace (workspace) {
59
- try {
60
- const auth =
61
- Buffer.from(this.user + ':' + this.password).toString('base64');
62
- const response = await fetch(this.url + 'workspaces/' + workspace + '/styles.json', {
63
- credentials: 'include',
64
- method: 'GET',
65
- headers: {
66
- Authorization: 'Basic ' + auth
67
- }
68
- });
69
-
70
- if (response.status === 200) {
71
- return await response.json();
72
- } else {
73
- console.warn(await response.text());
74
- return false;
56
+ const response = await fetch(this.url + 'workspaces/' + workspace + '/styles.json', {
57
+ credentials: 'include',
58
+ method: 'GET',
59
+ headers: {
60
+ Authorization: this.auth
75
61
  }
76
- } catch (error) {
77
- return false;
62
+ });
63
+
64
+ if (!response.ok) {
65
+ const geoServerResponse = await getGeoServerResponseText(response);
66
+ throw new GeoServerResponseError(null, geoServerResponse);
78
67
  }
68
+ return response.json();
79
69
  }
80
70
 
81
71
  /**
82
72
  * Returns all styles defined in workspaces.
83
73
  *
84
- * @returns {Object|Boolean} An object with all styles or 'false'
74
+ * @throws Error if request fails
75
+ *
76
+ * @returns {Object[]} An array with all style objects
85
77
  */
86
78
  async getAllWorkspaceStyles () {
87
- try {
88
- const allStyles = [];
89
- const ws = new WorkspaceClient(this.url, this.user, this.password);
90
- const allWs = await ws.getAll();
91
-
92
- // go over all workspaces and query the styles for
93
- for (let i = 0; i < allWs.workspaces.workspace.length; i++) {
94
- const ws = allWs.workspaces.workspace[i];
95
- const wsStyles = await this.getInWorkspace(ws.name);
96
-
97
- if (wsStyles.styles.style) {
98
- wsStyles.styles.style.forEach(wsStyle => {
99
- allStyles.push(wsStyle);
100
- });
101
- }
102
- }
79
+ const allStyles = [];
80
+ const ws = new WorkspaceClient(this.url, this.auth);
81
+ const allWs = await ws.getAll();
82
+
83
+ if (!allWs ||
84
+ !allWs.workspaces ||
85
+ !allWs.workspaces.workspace ||
86
+ !Array.isArray(allWs.workspaces.workspace)) {
87
+ throw new GeoServerResponseError('Response of available workspaces is malformed');
88
+ }
89
+
90
+ // go over all workspaces and query the styles for
91
+ for (let i = 0; i < allWs.workspaces.workspace.length; i++) {
92
+ const ws = allWs.workspaces.workspace[i];
93
+ const wsStyles = await this.getInWorkspace(ws.name);
103
94
 
104
- return allStyles;
105
- } catch (error) {
106
- return false;
95
+ if (wsStyles.styles.style) {
96
+ wsStyles.styles.style.forEach(wsStyle => {
97
+ allStyles.push(wsStyle);
98
+ });
99
+ }
107
100
  }
101
+
102
+ return allStyles;
108
103
  }
109
104
 
110
105
  /**
111
106
  * Returns all styles as combined object (default ones and those in
112
107
  * workspaces).
113
108
  *
114
- * @returns {Object|Boolean} An object with all styles or 'false'
109
+ * @returns {Object[]} An array with all style objects
115
110
  */
116
111
  async getAll () {
117
- try {
118
- const defaultStyles = await this.getDefaults();
119
- const wsStyles = await this.getAllWorkspaceStyles();
120
- const allStyles = defaultStyles.styles.style.concat(wsStyles);
121
-
122
- return allStyles;
123
- } catch (error) {
124
- return false;
112
+ const defaultStyles = await this.getDefaults();
113
+ const wsStyles = await this.getAllWorkspaceStyles();
114
+ if (
115
+ !defaultStyles ||
116
+ !defaultStyles.styles ||
117
+ !defaultStyles.styles.style ||
118
+ !Array.isArray(defaultStyles.styles.style)
119
+ ) {
120
+ throw new GeoServerResponseError('Response of default styles malformed')
125
121
  }
122
+ const allStyles = defaultStyles.styles.style.concat(wsStyles);
123
+
124
+ return allStyles;
126
125
  }
127
126
 
128
127
  /**
129
128
  * Publishes a new SLD style.
130
129
  *
131
- * @param {String} workspace The workspace to publish style in
130
+ * @param {String} workspace The workspace to publish the style in
132
131
  * @param {String} name Name of the style
133
132
  * @param {String} sldBody SLD style (as XML text)
134
133
  *
135
- * @returns {Boolean} If the style could be published
134
+ * @throws Error if request fails
136
135
  */
137
136
  async publish (workspace, name, sldBody) {
138
- try {
139
- const auth = Buffer.from(this.user + ':' + this.password).toString('base64');
140
- const response = await fetch(this.url + 'workspaces/' + workspace + '/styles?name=' + name, {
141
- credentials: 'include',
142
- method: 'POST',
143
- headers: {
144
- Authorization: 'Basic ' + auth,
145
- 'Content-Type': 'application/vnd.ogc.sld+xml'
146
- },
147
- body: sldBody
148
- });
149
-
150
- if (response.status === 201) {
151
- return true;
152
- } else {
153
- console.warn(await response.text());
154
- return false;
137
+ const response = await fetch(this.url + 'workspaces/' + workspace + '/styles?name=' + name, {
138
+ credentials: 'include',
139
+ method: 'POST',
140
+ headers: {
141
+ Authorization: this.auth,
142
+ 'Content-Type': 'application/vnd.ogc.sld+xml'
143
+ },
144
+ body: sldBody
145
+ });
146
+
147
+ if (!response.ok) {
148
+ const geoServerResponse = await getGeoServerResponseText(response);
149
+ throw new GeoServerResponseError(null, geoServerResponse);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Deletes a style.
155
+ *
156
+ * @param {String} workspace The name of the workspace, can be undefined if style is not assigned to a workspace
157
+ * @param {String} name The name of the style to delete
158
+ * @param {Boolean} [recurse=false] If references to the specified style in existing layers should be deleted
159
+ * @param {Boolean} [purge=false] Whether the underlying file containing the style should be deleted on disk
160
+ */
161
+ async delete (workspace, name, recurse, purge) {
162
+ let paramPurge = false;
163
+ let paramRecurse = false;
164
+
165
+ if (purge === true) {
166
+ paramPurge = true;
167
+ }
168
+ if (recurse === true) {
169
+ paramRecurse = true;
170
+ }
171
+
172
+ let endpoint;
173
+
174
+ if (workspace) {
175
+ // delete style inside workspace
176
+ endpoint = this.url + 'workspaces/' + workspace + '/styles/' + name +
177
+ '?' + 'purge=' + paramPurge + '&' + 'recurse=' + paramRecurse;
178
+ } else {
179
+ // delete style without workspace
180
+ endpoint = this.url + 'styles/' + name +
181
+ '?' + 'purge=' + paramPurge + '&' + 'recurse=' + paramRecurse;
182
+ }
183
+
184
+ const response = await fetch(endpoint, {
185
+ credentials: 'include',
186
+ method: 'DELETE',
187
+ headers: {
188
+ Authorization: this.auth
189
+ }
190
+ });
191
+
192
+ if (!response.ok) {
193
+ const geoServerResponse = await getGeoServerResponseText(response);
194
+ switch (response.status) {
195
+ case 403:
196
+ throw new GeoServerResponseError(
197
+ 'Deletion failed. There might be dependant layers to this style. Delete them first or call this with "recurse=false"',
198
+ geoServerResponse
199
+ );
200
+ default:
201
+ throw new GeoServerResponseError(null, geoServerResponse);
155
202
  }
156
- } catch (error) {
157
- return false;
158
203
  }
159
204
  }
160
205
 
161
206
  /**
162
207
  * Assigns a style to a layer.
163
208
  *
164
- * @param {String} qualifiedName GeoServer layer name with workspace prefix
209
+ * @param {String} workspaceOfLayer The name of the layer's workspace, can be undefined
210
+ * @param {String} layerName The name of the layer to query
211
+ * @param {String} workspaceOfStyle The workspace of the style, can be undefined
165
212
  * @param {String} styleName The name of the style
166
- * @param {String} [workspaceStyle] The workspace of the style
167
213
  * @param {Boolean} [isDefaultStyle=true] If the style should be the default style of the layer
168
214
  *
169
- * @returns {Boolean} If the style could be assigned
215
+ * @throws Error if request fails
170
216
  */
171
- async assignStyleToLayer (qualifiedName, styleName, workspaceStyle, isDefaultStyle) {
172
- try {
173
- const auth = Buffer.from(this.user + ':' + this.password).toString('base64');
174
-
175
- const styleBody = await this.getStyleInformation(styleName, workspaceStyle);
176
-
177
- const response = await fetch(this.url + 'layers/' + qualifiedName + '/styles?default=' + isDefaultStyle, {
178
- credentials: 'include',
179
- method: 'POST',
180
- headers: {
181
- Authorization: 'Basic ' + auth,
182
- 'Content-Type': 'application/json'
183
- },
184
- body: JSON.stringify(styleBody)
185
- });
186
-
187
- if (response.status === 201) {
188
- return true;
189
- } else {
190
- console.warn(await response.text());
191
- return false;
192
- }
193
- } catch (error) {
194
- return false;
217
+ async assignStyleToLayer (workspaceOfLayer, layerName, workspaceOfStyle, styleName, isDefaultStyle) {
218
+ let qualifiedName;
219
+ if (workspaceOfLayer) {
220
+ qualifiedName = `${workspaceOfLayer}:${layerName}`;
221
+ } else {
222
+ qualifiedName = layerName;
223
+ }
224
+ const styleBody = await this.getStyleInformation(workspaceOfStyle, styleName);
225
+
226
+ let url;
227
+ // we set the style as defaultStyle, unless user explicitly provides 'false'
228
+ if (isDefaultStyle !== false) {
229
+ url = this.url + 'layers/' + qualifiedName + '/styles?default=true';
230
+ } else {
231
+ url = this.url + 'layers/' + qualifiedName + '/styles';
232
+ }
233
+
234
+ const response = await fetch(url, {
235
+ credentials: 'include',
236
+ method: 'POST',
237
+ headers: {
238
+ Authorization: this.auth,
239
+ 'Content-Type': 'application/json'
240
+ },
241
+ body: JSON.stringify(styleBody)
242
+ });
243
+
244
+ if (!response.ok) {
245
+ const geoServerResponse = await getGeoServerResponseText(response);
246
+ throw new GeoServerResponseError(null, geoServerResponse);
195
247
  }
196
248
  }
197
249
 
198
250
  /**
199
251
  * Get information about a style.
200
252
  *
253
+ * @param {String} workspace The name of the workspace, can be undefined
201
254
  * @param {String} styleName The name of the style
202
- * @param {String} [workspace] The name of the workspace
203
255
  *
204
- * @returns {Object|Boolean} An object about the style or 'false'
256
+ * @throws Error if request fails
257
+ *
258
+ * @returns {Object} An object about the style or undefined if it cannot be found
205
259
  */
206
- async getStyleInformation (styleName, workspace) {
207
- try {
208
- const auth =
209
- Buffer.from(this.user + ':' + this.password).toString('base64');
210
-
211
- let url;
212
- if (workspace) {
213
- url = this.url + 'workspaces/' + workspace + '/styles/' + styleName + '.json';
214
- } else {
215
- url = this.url + 'styles/' + styleName + '.json';
216
- }
260
+ async getStyleInformation (workspace, styleName) {
261
+ let url;
262
+ if (workspace) {
263
+ url = this.url + 'workspaces/' + workspace + '/styles/' + styleName + '.json';
264
+ } else {
265
+ url = this.url + 'styles/' + styleName + '.json';
266
+ }
217
267
 
218
- const response = await fetch(url, {
219
- credentials: 'include',
220
- method: 'GET',
221
- headers: {
222
- Authorization: 'Basic ' + auth
223
- }
224
- });
268
+ const response = await fetch(url, {
269
+ credentials: 'include',
270
+ method: 'GET',
271
+ headers: {
272
+ Authorization: this.auth
273
+ }
274
+ });
225
275
 
226
- if (response.status === 200) {
227
- return await response.json();
276
+ if (!response.ok) {
277
+ const grc = new AboutClient(this.url, this.auth);
278
+ if (await grc.exists()) {
279
+ // GeoServer exists, but requested item does not exist, we return empty
280
+ return;
228
281
  } else {
229
- console.warn(await response.text());
230
- return false;
282
+ // There was a general problem with GeoServer
283
+ const geoServerResponse = await getGeoServerResponseText(response);
284
+ throw new GeoServerResponseError(null, geoServerResponse);
231
285
  }
232
- } catch (error) {
233
- return false;
234
286
  }
287
+ return response.json();
235
288
  }
236
289
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Utility functions and classes
3
+ */
4
+
5
+ /**
6
+ * Return the GeoServer response text if available.
7
+ *
8
+ * @param {Response} response The response of the GeoServer
9
+ *
10
+ * @returns {String} The response text if available
11
+ */
12
+ async function getGeoServerResponseText (response) {
13
+ try {
14
+ return response.text()
15
+ } catch (e) {
16
+ // return nothing
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Generic GeoServer error
22
+ */
23
+ class GeoServerResponseError extends Error {
24
+ /**
25
+ * @param {String} [message=GeoServer Response Error] The error message
26
+ * @param {String} [geoServerOutput] The error output from GeoServer (useful for debugging)
27
+ */
28
+ constructor (message, geoServerOutput) {
29
+ super(message)
30
+ this.name = 'GeoServerResponseError';
31
+ this.message = message || 'GeoServer Response Error'
32
+
33
+ // custom property as explained here: https://xjamundx.medium.com/custom-javascript-errors-in-es6-aa891b173f87
34
+ this.geoServerOutput = geoServerOutput;
35
+ }
36
+ }
37
+
38
+ export {
39
+ getGeoServerResponseText,
40
+ GeoServerResponseError
41
+ }