@wordpress/abilities 0.1.1-next.dc3f6d3c1.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.
Files changed (104) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE.md +788 -0
  3. package/README.md +302 -0
  4. package/build/api.js +189 -0
  5. package/build/api.js.map +7 -0
  6. package/build/index.js +53 -0
  7. package/build/index.js.map +7 -0
  8. package/build/store/actions.js +183 -0
  9. package/build/store/actions.js.map +7 -0
  10. package/build/store/constants.js +58 -0
  11. package/build/store/constants.js.map +7 -0
  12. package/build/store/index.js +77 -0
  13. package/build/store/index.js.map +7 -0
  14. package/build/store/reducer.js +126 -0
  15. package/build/store/reducer.js.map +7 -0
  16. package/build/store/resolvers.js +107 -0
  17. package/build/store/resolvers.js.map +7 -0
  18. package/build/store/selectors.js +64 -0
  19. package/build/store/selectors.js.map +7 -0
  20. package/build/store/tests/actions.test.js +792 -0
  21. package/build/store/tests/actions.test.js.map +7 -0
  22. package/build/store/tests/reducer.test.js +743 -0
  23. package/build/store/tests/reducer.test.js.map +7 -0
  24. package/build/store/tests/resolvers.test.js +520 -0
  25. package/build/store/tests/resolvers.test.js.map +7 -0
  26. package/build/store/tests/selectors.test.js +349 -0
  27. package/build/store/tests/selectors.test.js.map +7 -0
  28. package/build/tests/api.test.js +546 -0
  29. package/build/tests/api.test.js.map +7 -0
  30. package/build/tests/validation.test.js +453 -0
  31. package/build/tests/validation.test.js.map +7 -0
  32. package/build/types.js +19 -0
  33. package/build/types.js.map +7 -0
  34. package/build/validation.js +144 -0
  35. package/build/validation.js.map +7 -0
  36. package/build-module/api.js +146 -0
  37. package/build-module/api.js.map +7 -0
  38. package/build-module/index.js +28 -0
  39. package/build-module/index.js.map +7 -0
  40. package/build-module/store/actions.js +161 -0
  41. package/build-module/store/actions.js.map +7 -0
  42. package/build-module/store/constants.js +24 -0
  43. package/build-module/store/constants.js.map +7 -0
  44. package/build-module/store/index.js +47 -0
  45. package/build-module/store/index.js.map +7 -0
  46. package/build-module/store/reducer.js +112 -0
  47. package/build-module/store/reducer.js.map +7 -0
  48. package/build-module/store/resolvers.js +84 -0
  49. package/build-module/store/resolvers.js.map +7 -0
  50. package/build-module/store/selectors.js +36 -0
  51. package/build-module/store/selectors.js.map +7 -0
  52. package/build-module/store/tests/actions.test.js +804 -0
  53. package/build-module/store/tests/actions.test.js.map +7 -0
  54. package/build-module/store/tests/reducer.test.js +726 -0
  55. package/build-module/store/tests/reducer.test.js.map +7 -0
  56. package/build-module/store/tests/resolvers.test.js +523 -0
  57. package/build-module/store/tests/resolvers.test.js.map +7 -0
  58. package/build-module/store/tests/selectors.test.js +352 -0
  59. package/build-module/store/tests/selectors.test.js.map +7 -0
  60. package/build-module/tests/api.test.js +530 -0
  61. package/build-module/tests/api.test.js.map +7 -0
  62. package/build-module/tests/validation.test.js +451 -0
  63. package/build-module/tests/validation.test.js.map +7 -0
  64. package/build-module/types.js +1 -0
  65. package/build-module/types.js.map +7 -0
  66. package/build-module/validation.js +109 -0
  67. package/build-module/validation.js.map +7 -0
  68. package/build-types/api.d.ts +138 -0
  69. package/build-types/api.d.ts.map +1 -0
  70. package/build-types/index.d.ts +41 -0
  71. package/build-types/index.d.ts.map +1 -0
  72. package/build-types/store/actions.d.ts +76 -0
  73. package/build-types/store/actions.d.ts.map +1 -0
  74. package/build-types/store/constants.d.ts +14 -0
  75. package/build-types/store/constants.d.ts.map +1 -0
  76. package/build-types/store/index.d.ts +7 -0
  77. package/build-types/store/index.d.ts.map +1 -0
  78. package/build-types/store/reducer.d.ts +22 -0
  79. package/build-types/store/reducer.d.ts.map +1 -0
  80. package/build-types/store/resolvers.d.ts +47 -0
  81. package/build-types/store/resolvers.d.ts.map +1 -0
  82. package/build-types/store/selectors.d.ts +37 -0
  83. package/build-types/store/selectors.d.ts.map +1 -0
  84. package/build-types/types.d.ts +163 -0
  85. package/build-types/types.d.ts.map +1 -0
  86. package/build-types/validation.d.ts +23 -0
  87. package/build-types/validation.d.ts.map +1 -0
  88. package/package.json +52 -0
  89. package/src/api.ts +339 -0
  90. package/src/index.ts +65 -0
  91. package/src/store/actions.ts +247 -0
  92. package/src/store/constants.ts +15 -0
  93. package/src/store/index.ts +55 -0
  94. package/src/store/reducer.ts +184 -0
  95. package/src/store/resolvers.ts +151 -0
  96. package/src/store/selectors.ts +82 -0
  97. package/src/store/tests/actions.test.ts +950 -0
  98. package/src/store/tests/reducer.test.ts +861 -0
  99. package/src/store/tests/resolvers.test.ts +642 -0
  100. package/src/store/tests/selectors.test.ts +412 -0
  101. package/src/tests/api.test.ts +655 -0
  102. package/src/tests/validation.test.ts +532 -0
  103. package/src/types.ts +190 -0
  104. package/src/validation.ts +207 -0
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Schema validation for client-side ability input and output schemas using AJV and ajv-formats.
3
+ *
4
+ * This utility provides validation for JSON Schema draft-04.
5
+ * Rules are configured to support the intersection of common rules between JSON Schema draft-04, WordPress (a subset of JSON Schema draft-04),
6
+ * and various providers like OpenAI and Anthropic.
7
+ *
8
+ * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/#json-schema-basics
9
+ */
10
+
11
+ /**
12
+ * External dependencies
13
+ */
14
+ import Ajv from 'ajv-draft-04';
15
+ import addFormats from 'ajv-formats';
16
+
17
+ /**
18
+ * Internal dependencies
19
+ */
20
+ import type { ValidationError } from './types';
21
+
22
+ const ajv = new Ajv( {
23
+ coerceTypes: false, // No type coercion - AI should send proper JSON
24
+ useDefaults: true,
25
+ removeAdditional: false, // Keep additional properties
26
+ allErrors: true,
27
+ verbose: true,
28
+ allowUnionTypes: true, // Allow anyOf without explicit type
29
+ } );
30
+
31
+ addFormats( ajv, [ 'date-time', 'email', 'hostname', 'ipv4', 'ipv6', 'uuid' ] );
32
+
33
+ /**
34
+ * Formats AJV errors into a simple error message.
35
+ * The Abilities API will wrap this with ability_invalid_input/output to match the server side format.
36
+ *
37
+ * @param ajvError The AJV validation error.
38
+ * @param param The base parameter name.
39
+ * @return Simple error message string.
40
+ */
41
+ function formatAjvError( ajvError: any, param: string ): string {
42
+ // Convert AJV's instancePath format (/0/prop) to an array like format to better match WordPress ([0][prop])
43
+ const instancePath = ajvError.instancePath
44
+ ? ajvError.instancePath.replace( /\//g, '][' ).replace( /^\]\[/, '[' ) +
45
+ ']'
46
+ : '';
47
+ const fullParam = param + instancePath;
48
+
49
+ switch ( ajvError.keyword ) {
50
+ case 'type':
51
+ return `${ fullParam } is not of type ${ ajvError.params.type }.`;
52
+
53
+ case 'required':
54
+ return `${ ajvError.params.missingProperty } is a required property of ${ fullParam }.`;
55
+
56
+ case 'additionalProperties':
57
+ return `${ ajvError.params.additionalProperty } is not a valid property of Object.`;
58
+
59
+ case 'enum':
60
+ const enumValues = ajvError.params.allowedValues
61
+ .map( ( v: any ) =>
62
+ typeof v === 'string' ? v : JSON.stringify( v )
63
+ )
64
+ .join( ', ' );
65
+ return ajvError.params.allowedValues.length === 1
66
+ ? `${ fullParam } is not ${ enumValues }.`
67
+ : `${ fullParam } is not one of ${ enumValues }.`;
68
+
69
+ case 'pattern':
70
+ return `${ fullParam } does not match pattern ${ ajvError.params.pattern }.`;
71
+
72
+ case 'format':
73
+ const format = ajvError.params.format;
74
+ const formatMessages: Record< string, string > = {
75
+ email: 'Invalid email address.',
76
+ 'date-time': 'Invalid date.',
77
+ uuid: `${ fullParam } is not a valid UUID.`,
78
+ ipv4: `${ fullParam } is not a valid IP address.`,
79
+ ipv6: `${ fullParam } is not a valid IP address.`,
80
+ hostname: `${ fullParam } is not a valid hostname.`,
81
+ };
82
+ return formatMessages[ format ] || `Invalid ${ format }.`;
83
+
84
+ case 'minimum':
85
+ case 'exclusiveMinimum':
86
+ return ajvError.keyword === 'exclusiveMinimum'
87
+ ? `${ fullParam } must be greater than ${ ajvError.params.limit }`
88
+ : `${ fullParam } must be greater than or equal to ${ ajvError.params.limit }`;
89
+
90
+ case 'maximum':
91
+ case 'exclusiveMaximum':
92
+ return ajvError.keyword === 'exclusiveMaximum'
93
+ ? `${ fullParam } must be less than ${ ajvError.params.limit }`
94
+ : `${ fullParam } must be less than or equal to ${ ajvError.params.limit }`;
95
+
96
+ case 'multipleOf':
97
+ return `${ fullParam } must be a multiple of ${ ajvError.params.multipleOf }.`;
98
+
99
+ case 'anyOf':
100
+ case 'oneOf':
101
+ return `${ fullParam } is invalid (failed ${ ajvError.keyword } validation).`;
102
+
103
+ case 'minLength':
104
+ return `${ fullParam } must be at least ${
105
+ ajvError.params.limit
106
+ } character${ ajvError.params.limit === 1 ? '' : 's' } long.`;
107
+
108
+ case 'maxLength':
109
+ return `${ fullParam } must be at most ${
110
+ ajvError.params.limit
111
+ } character${ ajvError.params.limit === 1 ? '' : 's' } long.`;
112
+
113
+ case 'minItems':
114
+ return `${ fullParam } must contain at least ${
115
+ ajvError.params.limit
116
+ } item${ ajvError.params.limit === 1 ? '' : 's' }.`;
117
+
118
+ case 'maxItems':
119
+ return `${ fullParam } must contain at most ${
120
+ ajvError.params.limit
121
+ } item${ ajvError.params.limit === 1 ? '' : 's' }.`;
122
+
123
+ case 'uniqueItems':
124
+ return `${ fullParam } has duplicate items.`;
125
+
126
+ case 'minProperties':
127
+ return `${ fullParam } must contain at least ${
128
+ ajvError.params.limit
129
+ } propert${ ajvError.params.limit === 1 ? 'y' : 'ies' }.`;
130
+
131
+ case 'maxProperties':
132
+ return `${ fullParam } must contain at most ${
133
+ ajvError.params.limit
134
+ } propert${ ajvError.params.limit === 1 ? 'y' : 'ies' }.`;
135
+
136
+ default:
137
+ // Fallback for any unhandled validation keywords
138
+ return (
139
+ ajvError.message ||
140
+ `${ fullParam } is invalid (failed ${ ajvError.keyword } validation).`
141
+ );
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Validates a value against a JSON Schema.
147
+ *
148
+ * @param value The value to validate.
149
+ * @param args The JSON Schema to validate against.
150
+ * @param param Optional parameter name for error messages.
151
+ * @return True if valid, error message string if invalid.
152
+ */
153
+ export function validateValueFromSchema(
154
+ value: any,
155
+ args: Record< string, any >,
156
+ param = ''
157
+ ): true | ValidationError {
158
+ // WordPress server expects schema to be an array/object
159
+ if ( ! args || typeof args !== 'object' ) {
160
+ // WordPress issues a _doing_it_wrong for invalid schema
161
+ // Match this behavior with console.warn on client-side
162
+ // eslint-disable-next-line no-console
163
+ console.warn( `Schema must be an object. Received ${ typeof args }.` );
164
+ // Continue validation, treating as valid (matching server behavior)
165
+ return true;
166
+ }
167
+
168
+ // Type validation - WordPress REST API requires type to be set
169
+ if ( ! args.type && ! args.anyOf && ! args.oneOf ) {
170
+ // WordPress issues a _doing_it_wrong but continues
171
+ // eslint-disable-next-line no-console
172
+ console.warn(
173
+ `The "type" schema keyword for ${ param || 'value' } is required.`
174
+ );
175
+ return true;
176
+ }
177
+
178
+ try {
179
+ const { default: defaultValue, ...schemaWithoutDefault } = args;
180
+ const validate = ajv.compile( schemaWithoutDefault );
181
+ const valid = validate( value === undefined ? defaultValue : value );
182
+
183
+ if ( valid ) {
184
+ return true;
185
+ }
186
+
187
+ // Return the first error as a simple message string
188
+ // The API will wrap this with ability_invalid_input/output
189
+ if ( validate.errors && validate.errors.length > 0 ) {
190
+ // For anyOf/oneOf, look for the more specific error
191
+ const anyOfError = validate.errors.find(
192
+ ( e ) => e.keyword === 'anyOf' || e.keyword === 'oneOf'
193
+ );
194
+ if ( anyOfError ) {
195
+ return formatAjvError( anyOfError, param );
196
+ }
197
+ return formatAjvError( validate.errors[ 0 ], param );
198
+ }
199
+
200
+ return `${ param } is invalid.`;
201
+ } catch ( error ) {
202
+ // Handle schema compilation errors
203
+ // eslint-disable-next-line no-console
204
+ console.error( 'Schema compilation error:', error );
205
+ return 'Invalid schema provided for validation.';
206
+ }
207
+ }