@zapier/policy-context 0.10.0 → 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/dist/index.cjs CHANGED
@@ -21,14 +21,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AccessLevelSchema: () => AccessLevelSchema,
24
+ ActionRunContextInputSchema: () => ActionRunContextInputSchema,
24
25
  ActionRunContextSchema: () => ActionRunContextSchema,
26
+ ActionTypeSchema: () => ActionTypeSchema,
25
27
  BuildActionRunContextOptionsSchema: () => BuildActionRunContextOptionsSchema,
26
28
  BuildHttpRequestContextOptionsSchema: () => BuildHttpRequestContextOptionsSchema,
27
29
  DEFAULT_PORTS: () => DEFAULT_PORTS,
28
30
  HttpRequestContextSchema: () => HttpRequestContextSchema,
29
31
  IGNORED_HEADERS: () => IGNORED_HEADERS,
30
- JsonValueSchema: () => import_policy_schema.JsonValueSchema,
31
32
  ParsedUrlSchema: () => ParsedUrlSchema,
33
+ READ_ACTION_TYPES: () => READ_ACTION_TYPES,
34
+ READ_HTTP_METHODS: () => READ_HTTP_METHODS,
32
35
  RequestContextSchema: () => RequestContextSchema,
33
36
  buildActionResource: () => buildActionResource,
34
37
  buildActionRunContext: () => buildActionRunContext,
@@ -39,6 +42,8 @@ __export(index_exports, {
39
42
  buildExactHttpConditions: () => buildExactHttpConditions,
40
43
  buildHttpRequestContext: () => buildHttpRequestContext,
41
44
  buildUrlString: () => buildUrlString,
45
+ isWriteRequest: () => isWriteRequest,
46
+ parseActionResource: () => parseActionResource,
42
47
  parseUrl: () => parseUrl,
43
48
  toExactPolicy: () => toExactPolicy
44
49
  });
@@ -46,13 +51,20 @@ module.exports = __toCommonJS(index_exports);
46
51
 
47
52
  // src/types.ts
48
53
  var import_zod = require("zod");
49
- var import_policy_schema = require("@zapier/policy-schema");
54
+ var import_zod_to_openapi = require("@asteasolutions/zod-to-openapi");
55
+ (0, import_zod_to_openapi.extendZodWithOpenApi)(import_zod.z);
50
56
  var ParsedUrlSchema = import_zod.z.object({
51
- scheme: import_zod.z.string(),
52
- host: import_zod.z.string(),
53
- port: import_zod.z.number(),
54
- path: import_zod.z.string(),
55
- query: import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.string()))
57
+ scheme: import_zod.z.string().openapi({ description: 'URL scheme, e.g. "https"' }),
58
+ host: import_zod.z.string().openapi({ description: 'Hostname, e.g. "api.example.com"' }),
59
+ port: import_zod.z.number().int().openapi({
60
+ description: "Port number (defaults to 443 for https, 80 for http)"
61
+ }),
62
+ path: import_zod.z.string().openapi({ description: 'URL path, e.g. "/v1/foo"' }),
63
+ query: import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.string())).openapi({
64
+ description: "Query parameters as key to string array mapping"
65
+ })
66
+ }).openapi("ParsedUrl", {
67
+ description: "Parsed URL components for policy condition matching"
56
68
  });
57
69
  var DEFAULT_PORTS = {
58
70
  http: 80,
@@ -87,34 +99,137 @@ function buildUrlString(parsed) {
87
99
  return `${scheme}://${host}${portString}${path}${queryString ? "?" + queryString : ""}`;
88
100
  }
89
101
  var HttpRequestContextSchema = import_zod.z.object({
90
- request_type: import_zod.z.literal("http_request"),
91
- method: import_zod.z.string(),
102
+ request_type: import_zod.z.literal("http_request").openapi({ description: "Context type discriminator" }),
103
+ method: import_zod.z.string().max(10).transform((m) => m.toUpperCase()).openapi({ description: "HTTP method" }),
92
104
  /** Parsed URL components for policy condition matching */
93
105
  url: ParsedUrlSchema,
94
106
  /** Filtered headers (IGNORED_HEADERS excluded, keys lowercased) */
95
- headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()),
107
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).openapi({
108
+ description: "Filtered headers (IGNORED_HEADERS excluded, keys lowercased)"
109
+ }),
96
110
  /** Raw body string */
97
- body: import_zod.z.string().nullable(),
98
- /** Parsed JSON body */
99
- body_json: import_policy_schema.JsonValueSchema.nullable(),
111
+ body: import_zod.z.string().max(65536).nullable().openapi({ description: "Raw body string" }),
112
+ /**
113
+ * Parsed JSON body. Authored as `z.unknown().pipe(z.json())` so
114
+ * zod-to-openapi sees the outer `z.unknown()` and emits a permissive
115
+ * field (no recursive `$ref`, no type), while the inner `z.json()`
116
+ * still rejects non-JSON values (`Date`, `undefined`, functions) at
117
+ * parse time. Same inferred `JsonValue` type either way.
118
+ */
119
+ body_json: import_zod.z.unknown().pipe(import_zod.z.json()).nullable().describe("Parsed JSON body (if applicable)"),
100
120
  /** Zapier connection ID used for this request */
101
- connection_id: import_zod.z.string().optional(),
121
+ connection_id: import_zod.z.string().max(255).optional().openapi({ description: "Zapier connection ID used for this request" }),
102
122
  /** Optional human-readable reason for the request */
103
- reason: import_zod.z.string().optional()
123
+ reason: import_zod.z.string().max(1024).optional().openapi({ description: "Optional human-readable reason for the request" })
124
+ }).openapi("HttpRequestContext");
125
+ var ActionTypeSchema = import_zod.z.enum([
126
+ "filter",
127
+ "read",
128
+ "read_bulk",
129
+ "run",
130
+ "search",
131
+ "search_and_write",
132
+ "search_or_write",
133
+ "write"
134
+ ]).openapi("action_type", { description: "Canonical Zapier action type." });
135
+ var ACTION_RESOURCE_SEGMENT_PATTERN = /^[A-Za-z0-9._+-]+(?:@[A-Za-z0-9._+-]+)?$/;
136
+ function parseActionResource(resource) {
137
+ const segments = resource.split("/");
138
+ if (segments.length !== 4 || segments[0] !== "action") {
139
+ throw new Error(
140
+ `action resource must be action/{selected_api}/{action_type}/{action_key}: got ${resource}`
141
+ );
142
+ }
143
+ const [, selectedApi, actionTypeRaw, actionKey] = segments;
144
+ if (!selectedApi || !actionTypeRaw || !actionKey) {
145
+ throw new Error(`action resource has empty segment: ${resource}`);
146
+ }
147
+ const actionType = ActionTypeSchema.safeParse(actionTypeRaw);
148
+ if (!actionType.success) {
149
+ throw new Error(
150
+ `action resource has unknown action_type "${actionTypeRaw}": ${resource}`
151
+ );
152
+ }
153
+ if (!ACTION_RESOURCE_SEGMENT_PATTERN.test(selectedApi)) {
154
+ throw new Error(
155
+ `action resource has invalid selected_api segment "${selectedApi}": ${resource}`
156
+ );
157
+ }
158
+ if (!ACTION_RESOURCE_SEGMENT_PATTERN.test(actionKey)) {
159
+ throw new Error(
160
+ `action resource has invalid action_key segment "${actionKey}": ${resource}`
161
+ );
162
+ }
163
+ return {
164
+ selected_api: selectedApi,
165
+ action_type: actionType.data,
166
+ action_key: actionKey
167
+ };
168
+ }
169
+ var ActionRunContextInputSchema = import_zod.z.object({
170
+ request_type: import_zod.z.literal("action_run").openapi({ description: "Context type discriminator" }),
171
+ resource: import_zod.z.string().max(1024).optional().openapi({
172
+ description: "Canonical action resource, e.g. action/Slack@1.0.0/write/send_message"
173
+ }),
174
+ selected_api: import_zod.z.string().max(255).optional().openapi({
175
+ deprecated: true,
176
+ description: "Legacy: app identifier, e.g. Slack@1.0.0. Prefer `resource`."
177
+ }),
178
+ action_type: ActionTypeSchema.optional().openapi({
179
+ deprecated: true,
180
+ description: "Legacy: canonical Zapier action type. Prefer `resource`."
181
+ }),
182
+ action_key: import_zod.z.string().max(255).optional().openapi({
183
+ deprecated: true,
184
+ description: "Legacy: action key, e.g. send_message. Prefer `resource`."
185
+ }),
186
+ connection_id: import_zod.z.string().max(255).optional().openapi({ description: "Zapier connection ID used for this request" }),
187
+ /** Action input parameters. See body_json above for the pipe rationale. */
188
+ inputs: import_zod.z.unknown().pipe(import_zod.z.json()).optional().describe("Action input parameters"),
189
+ reason: import_zod.z.string().max(1024).optional().openapi({ description: "Optional human-readable reason for the request" })
190
+ }).openapi("ActionRunContext", {
191
+ description: "Action run context. The canonical shape carries the action identity in a single `resource` field of the form `action/{selected_api}/{action_type}/{action_key}`. The legacy `{selected_api, action_type, action_key}` shape is also accepted on input during the migration window \u2014 approvalsapi normalizes it to the canonical form before storage."
104
192
  });
105
- var ActionRunContextSchema = import_zod.z.object({
106
- request_type: import_zod.z.literal("action_run"),
107
- selected_api: import_zod.z.string(),
108
- action_type: import_zod.z.string(),
109
- action_key: import_zod.z.string(),
110
- connection_id: import_zod.z.string().optional(),
111
- inputs: import_policy_schema.JsonValueSchema.optional(),
112
- reason: import_zod.z.string().optional()
193
+ var ActionRunContextSchema = ActionRunContextInputSchema.transform(
194
+ (input, ctx) => {
195
+ const resource = input.resource ?? (input.selected_api && input.action_type && input.action_key ? `action/${input.selected_api}/${input.action_type}/${input.action_key}` : void 0);
196
+ if (resource === void 0) {
197
+ ctx.addIssue({
198
+ code: "custom",
199
+ message: "action_run context requires either `resource` or all of `selected_api`/`action_type`/`action_key`"
200
+ });
201
+ return import_zod.z.NEVER;
202
+ }
203
+ try {
204
+ parseActionResource(resource);
205
+ } catch (err) {
206
+ ctx.addIssue({
207
+ code: "custom",
208
+ message: err instanceof Error ? err.message : String(err)
209
+ });
210
+ return import_zod.z.NEVER;
211
+ }
212
+ return {
213
+ request_type: "action_run",
214
+ resource,
215
+ ...input.connection_id !== void 0 && {
216
+ connection_id: input.connection_id
217
+ },
218
+ ...input.inputs !== void 0 && { inputs: input.inputs },
219
+ ...input.reason !== void 0 && { reason: input.reason }
220
+ };
221
+ }
222
+ );
223
+ var RequestContextSchema = import_zod.z.union([HttpRequestContextSchema, ActionRunContextSchema]).openapi("RequestContext", {
224
+ description: "Union of request context shapes, keyed on `request_type`.",
225
+ discriminator: {
226
+ propertyName: "request_type",
227
+ mapping: {
228
+ http_request: "#/components/schemas/HttpRequestContext",
229
+ action_run: "#/components/schemas/ActionRunContext"
230
+ }
231
+ }
113
232
  });
114
- var RequestContextSchema = import_zod.z.discriminatedUnion("request_type", [
115
- HttpRequestContextSchema,
116
- ActionRunContextSchema
117
- ]);
118
233
 
119
234
  // src/constants.ts
120
235
  var IGNORED_HEADERS = /* @__PURE__ */ new Set([
@@ -148,74 +263,6 @@ var IGNORED_HEADERS = /* @__PURE__ */ new Set([
148
263
 
149
264
  // src/builders.ts
150
265
  var import_zod2 = require("zod");
151
- var BuildHttpRequestContextOptionsSchema = import_zod2.z.object({
152
- method: import_zod2.z.string(),
153
- /** Raw URL string. Will be normalized with https:// if no protocol present. */
154
- url: import_zod2.z.string(),
155
- /** Raw headers from the request. Will be filtered through IGNORED_HEADERS. */
156
- headers: import_zod2.z.record(
157
- import_zod2.z.string(),
158
- import_zod2.z.union([import_zod2.z.string(), import_zod2.z.array(import_zod2.z.string()), import_zod2.z.undefined()])
159
- ).optional(),
160
- /** Raw body string */
161
- body: import_zod2.z.string().nullable().optional(),
162
- /** Pre-parsed JSON body (e.g. from Fastify's automatic JSON parsing) */
163
- body_json: import_policy_schema.JsonValueSchema.optional(),
164
- /** Connection ID associated with this request */
165
- connection_id: import_zod2.z.string().optional()
166
- });
167
- function buildHttpRequestContext(options) {
168
- const rawUrl = options.url.includes("://") ? options.url : `https://${options.url}`;
169
- const url = parseUrl(rawUrl);
170
- const headers = {};
171
- if (options.headers) {
172
- for (const [key, value] of Object.entries(options.headers)) {
173
- const lowerKey = key.toLowerCase();
174
- if (!IGNORED_HEADERS.has(lowerKey) && typeof value === "string") {
175
- headers[lowerKey] = value;
176
- }
177
- }
178
- }
179
- let body_json = null;
180
- if (options.body_json !== void 0) {
181
- body_json = options.body_json;
182
- } else if (options.body) {
183
- const contentType = headers["content-type"];
184
- const isJson = typeof contentType === "string" && (contentType.includes("application/json") || contentType.includes("application/") && contentType.includes("+json"));
185
- if (isJson) {
186
- try {
187
- body_json = JSON.parse(options.body);
188
- } catch {
189
- }
190
- }
191
- }
192
- return HttpRequestContextSchema.parse({
193
- request_type: "http_request",
194
- method: options.method,
195
- url,
196
- headers,
197
- body: options.body ?? null,
198
- body_json,
199
- connection_id: options.connection_id
200
- });
201
- }
202
- var BuildActionRunContextOptionsSchema = import_zod2.z.object({
203
- selected_api: import_zod2.z.string(),
204
- action_type: import_zod2.z.string(),
205
- action_key: import_zod2.z.string(),
206
- connection_id: import_zod2.z.string().optional(),
207
- inputs: import_policy_schema.JsonValueSchema.optional()
208
- });
209
- function buildActionRunContext(options) {
210
- return ActionRunContextSchema.parse({
211
- request_type: "action_run",
212
- selected_api: options.selected_api,
213
- action_type: options.action_type,
214
- action_key: options.action_key,
215
- connection_id: options.connection_id,
216
- inputs: options.inputs
217
- });
218
- }
219
266
 
220
267
  // src/conditions.ts
221
268
  function buildExactHttpConditions(context) {
@@ -244,6 +291,12 @@ function buildExactHttpConditions(context) {
244
291
  operator: "equals",
245
292
  value: context.body_json
246
293
  });
294
+ } else if (context.body !== null) {
295
+ conditions.push({
296
+ path: ["body"],
297
+ operator: "equals",
298
+ value: context.body
299
+ });
247
300
  }
248
301
  for (const [key, value] of Object.entries(context.headers)) {
249
302
  if (!IGNORED_HEADERS.has(key.toLowerCase()) && typeof value === "string") {
@@ -267,7 +320,7 @@ function buildDomainHttpConditions(context) {
267
320
  }
268
321
  function buildExactActionConditions(context) {
269
322
  const conditions = [];
270
- if (context.inputs !== void 0 && context.inputs !== null && typeof context.inputs === "object") {
323
+ if (context.inputs !== void 0 && context.inputs !== null) {
271
324
  conditions.push({
272
325
  path: ["inputs"],
273
326
  operator: "equals",
@@ -276,11 +329,91 @@ function buildExactActionConditions(context) {
276
329
  }
277
330
  return conditions;
278
331
  }
279
- function buildActionResource(context) {
280
- return `action/${context.selected_api}/${context.action_type}/${context.action_key}`;
332
+ function buildActionResource(parts) {
333
+ return `action/${parts.selected_api}/${parts.action_type}/${parts.action_key}`;
281
334
  }
282
- function buildBroadActionResource(context) {
283
- return `action/${context.selected_api}/*/*`;
335
+ function buildBroadActionResource(parts) {
336
+ return `action/${parts.selected_api}/${parts.action_type}/*`;
337
+ }
338
+
339
+ // src/builders.ts
340
+ var BuildHttpRequestContextOptionsSchema = import_zod2.z.object({
341
+ method: import_zod2.z.string(),
342
+ /** Raw URL string. Will be normalized with https:// if no protocol present. */
343
+ url: import_zod2.z.string(),
344
+ /** Raw headers from the request. Will be filtered through IGNORED_HEADERS. */
345
+ headers: import_zod2.z.record(
346
+ import_zod2.z.string(),
347
+ import_zod2.z.union([import_zod2.z.string(), import_zod2.z.array(import_zod2.z.string()), import_zod2.z.undefined()])
348
+ ).optional(),
349
+ /** Raw body string */
350
+ body: import_zod2.z.string().nullable().optional(),
351
+ /** Pre-parsed JSON body (e.g. from Fastify's automatic JSON parsing) */
352
+ body_json: import_zod2.z.json().optional(),
353
+ /** Connection ID associated with this request */
354
+ connection_id: import_zod2.z.string().optional()
355
+ });
356
+ function buildHttpRequestContext(options) {
357
+ const parsed = BuildHttpRequestContextOptionsSchema.parse(options);
358
+ const rawUrl = parsed.url.includes("://") ? parsed.url : `https://${parsed.url}`;
359
+ const url = parseUrl(rawUrl);
360
+ const headers = {};
361
+ if (parsed.headers) {
362
+ for (const [key, value] of Object.entries(parsed.headers)) {
363
+ const lowerKey = key.toLowerCase();
364
+ if (!IGNORED_HEADERS.has(lowerKey) && typeof value === "string") {
365
+ headers[lowerKey] = value;
366
+ }
367
+ }
368
+ }
369
+ let body_json = null;
370
+ if (parsed.body_json !== void 0) {
371
+ body_json = parsed.body_json;
372
+ } else if (parsed.body) {
373
+ const contentType = headers["content-type"];
374
+ const isJson = typeof contentType === "string" && (contentType.includes("application/json") || contentType.includes("application/") && contentType.includes("+json"));
375
+ if (isJson) {
376
+ try {
377
+ body_json = JSON.parse(parsed.body);
378
+ } catch {
379
+ }
380
+ }
381
+ }
382
+ return HttpRequestContextSchema.parse({
383
+ request_type: "http_request",
384
+ method: parsed.method.toUpperCase(),
385
+ url,
386
+ headers,
387
+ body: parsed.body ?? null,
388
+ body_json,
389
+ connection_id: parsed.connection_id
390
+ });
391
+ }
392
+ var BuildActionRunContextOptionsSchema = import_zod2.z.object({
393
+ selected_api: import_zod2.z.string().regex(ACTION_RESOURCE_SEGMENT_PATTERN, {
394
+ message: "selected_api must contain only [A-Za-z0-9._@+-] (no globs, slashes, or whitespace)"
395
+ }),
396
+ action_type: ActionTypeSchema,
397
+ action_key: import_zod2.z.string().regex(ACTION_RESOURCE_SEGMENT_PATTERN, {
398
+ message: "action_key must contain only [A-Za-z0-9._@+-] (no globs, slashes, or whitespace)"
399
+ }),
400
+ connection_id: import_zod2.z.string().optional(),
401
+ inputs: import_zod2.z.json().optional(),
402
+ reason: import_zod2.z.string().optional()
403
+ });
404
+ function buildActionRunContext(options) {
405
+ const parsed = BuildActionRunContextOptionsSchema.parse(options);
406
+ return ActionRunContextSchema.parse({
407
+ request_type: "action_run",
408
+ resource: buildActionResource({
409
+ selected_api: parsed.selected_api,
410
+ action_type: parsed.action_type,
411
+ action_key: parsed.action_key
412
+ }),
413
+ connection_id: parsed.connection_id,
414
+ inputs: parsed.inputs,
415
+ reason: parsed.reason
416
+ });
284
417
  }
285
418
 
286
419
  // src/policies.ts
@@ -319,7 +452,7 @@ function actionContextToPolicy(context) {
319
452
  statements.push({
320
453
  effect: "allow",
321
454
  permissions: ["can_execute"],
322
- resources: [buildActionResource(context)],
455
+ resources: [context.resource],
323
456
  conditions: buildExactActionConditions(context)
324
457
  });
325
458
  return { version: 2, statements };
@@ -333,9 +466,28 @@ function toExactPolicy(context) {
333
466
 
334
467
  // src/connection-policy.ts
335
468
  var import_zod3 = require("zod");
469
+ function globSelectedApiVersion(selectedApi) {
470
+ const at = selectedApi.indexOf("@");
471
+ return at === -1 ? selectedApi : selectedApi.slice(0, at) + "@*";
472
+ }
336
473
  var AccessLevelSchema = import_zod3.z.enum(["read", "read_write"]);
337
474
  var READ_HTTP_METHODS = ["GET", "HEAD", "OPTIONS"];
338
- var READ_ACTION_TYPES = ["read", "search"];
475
+ var READ_ACTION_TYPES = [
476
+ "read",
477
+ "read_bulk",
478
+ "search"
479
+ ];
480
+ var READ_HTTP_METHOD_SET = new Set(READ_HTTP_METHODS);
481
+ var READ_ACTION_TYPE_SET = new Set(
482
+ READ_ACTION_TYPES
483
+ );
484
+ function isWriteRequest(context) {
485
+ if (context.request_type === "http_request") {
486
+ return !READ_HTTP_METHOD_SET.has(context.method);
487
+ }
488
+ const { action_type } = parseActionResource(context.resource);
489
+ return !READ_ACTION_TYPE_SET.has(action_type);
490
+ }
339
491
  function buildConnectionPolicy(options) {
340
492
  const { connectionId, selectedApi, accessLevel } = options;
341
493
  const authStatement = {
@@ -354,7 +506,7 @@ function buildConnectionPolicy(options) {
354
506
  httpConditions.push({
355
507
  path: ["method"],
356
508
  operator: "in",
357
- value: READ_HTTP_METHODS
509
+ value: [...READ_HTTP_METHODS]
358
510
  });
359
511
  }
360
512
  const httpStatement = {
@@ -363,19 +515,13 @@ function buildConnectionPolicy(options) {
363
515
  resources: ["http"],
364
516
  conditions: httpConditions
365
517
  };
366
- const actionConditions = [connectionCondition];
367
- if (accessLevel === "read") {
368
- actionConditions.push({
369
- path: ["action_type"],
370
- operator: "in",
371
- value: READ_ACTION_TYPES
372
- });
373
- }
518
+ const apiPattern = globSelectedApiVersion(selectedApi);
519
+ const actionResources = accessLevel === "read" ? READ_ACTION_TYPES.map((t) => `action/${apiPattern}/${t}/*`) : [`action/${apiPattern}/*/*`];
374
520
  const actionStatement = {
375
521
  effect: "allow",
376
522
  permissions: ["can_execute"],
377
- resources: [`action/${selectedApi}/*/*`],
378
- conditions: actionConditions
523
+ resources: actionResources,
524
+ conditions: [connectionCondition]
379
525
  };
380
526
  return {
381
527
  version: 2,
@@ -385,14 +531,17 @@ function buildConnectionPolicy(options) {
385
531
  // Annotate the CommonJS export names for ESM import in node:
386
532
  0 && (module.exports = {
387
533
  AccessLevelSchema,
534
+ ActionRunContextInputSchema,
388
535
  ActionRunContextSchema,
536
+ ActionTypeSchema,
389
537
  BuildActionRunContextOptionsSchema,
390
538
  BuildHttpRequestContextOptionsSchema,
391
539
  DEFAULT_PORTS,
392
540
  HttpRequestContextSchema,
393
541
  IGNORED_HEADERS,
394
- JsonValueSchema,
395
542
  ParsedUrlSchema,
543
+ READ_ACTION_TYPES,
544
+ READ_HTTP_METHODS,
396
545
  RequestContextSchema,
397
546
  buildActionResource,
398
547
  buildActionRunContext,
@@ -403,6 +552,8 @@ function buildConnectionPolicy(options) {
403
552
  buildExactHttpConditions,
404
553
  buildHttpRequestContext,
405
554
  buildUrlString,
555
+ isWriteRequest,
556
+ parseActionResource,
406
557
  parseUrl,
407
558
  toExactPolicy
408
559
  });