@@ -31,15 +31,32 @@ export function ashbyAuthHeaders(apiKey: string): Record<string, string> {
3131
3232/**
3333 * Extract a human-readable error message from an Ashby error response. Ashby
34- * returns errors as either `errorInfo.message` or an `errors` string array.
34+ * documents two shapes and uses both: `errorInfo.message`, and an `errors`
35+ * array whose entries are either plain strings or `{ message, parameter }`
36+ * objects. An object entry stringifies to `[object Object]` unless its message
37+ * is read explicitly, which is the form a 403 for a missing module permission
38+ * arrives in.
3539 */
3640export function ashbyErrorMessage ( data : unknown , fallback : string ) : string {
3741 if ( ! data || typeof data !== 'object' ) return fallback
3842 const d = data as Unknown
3943 const info = d . errorInfo as Unknown | undefined
4044 if ( info && typeof info . message === 'string' && info . message ) return info . message
4145 if ( Array . isArray ( d . errors ) && d . errors . length > 0 ) {
42- return d . errors . map ( ( e ) => String ( e ) ) . join ( '; ' )
46+ const messages = d . errors
47+ . map ( ( e ) => {
48+ if ( typeof e === 'string' ) return e
49+ if ( e && typeof e === 'object' ) {
50+ const entry = e as Unknown
51+ const message = typeof entry . message === 'string' ? entry . message : ''
52+ const parameter = typeof entry . parameter === 'string' ? entry . parameter : ''
53+ if ( message && parameter ) return `${ message } (${ parameter } )`
54+ if ( message ) return message
55+ }
56+ return ''
57+ } )
58+ . filter ( Boolean )
59+ if ( messages . length > 0 ) return messages . join ( '; ' )
4360 }
4461 return fallback
4562}
@@ -59,18 +76,25 @@ function mapContactArray(raw: unknown): AshbyContactInfo[] {
5976 return raw . map ( ( c ) => mapContact ( c ) ) . filter ( ( c ) : c is AshbyContactInfo => c !== null )
6077}
6178
79+ /**
80+ * Map a single custom field value as returned on an object. Ashby returns
81+ * `valueLabel` as a string for ValueSelect fields and an array of strings for
82+ * MultiValueSelect, and omits it entirely for every other field type.
83+ */
84+ export function mapCustomFieldOnObject ( raw : unknown ) : AshbyCustomField {
85+ const cf = ( raw ?? { } ) as Unknown
86+ return {
87+ id : ( cf . id as string ) ?? null ,
88+ title : ( cf . title as string ) ?? '' ,
89+ isPrivate : ( cf . isPrivate as boolean ) ?? false ,
90+ valueLabel : ( cf . valueLabel as string | string [ ] ) ?? null ,
91+ value : cf . value ?? null ,
92+ }
93+ }
94+
6295function mapCustomFields ( raw : unknown ) : AshbyCustomField [ ] {
6396 if ( ! Array . isArray ( raw ) ) return [ ]
64- return raw . map ( ( f ) => {
65- const cf = f as Unknown
66- return {
67- id : ( cf . id as string ) ?? null ,
68- title : ( cf . title as string ) ?? '' ,
69- isPrivate : ( cf . isPrivate as boolean ) ?? false ,
70- valueLabel : ( cf . valueLabel as string ) ?? null ,
71- value : cf . value ?? null ,
72- }
73- } )
97+ return raw . map ( mapCustomFieldOnObject )
7498}
7599
76100function mapFileHandle ( raw : unknown ) : AshbyFileHandle | null {
@@ -373,18 +397,30 @@ export const CONTACT_INFO_OUTPUT = {
373397 } ,
374398} as const satisfies OutputProperty
375399
400+ /**
401+ * Shape of a custom field as it exists on an Application, Candidate, Job, or
402+ * Opening - the value, not the field definition. Shared by every tool that
403+ * reads or writes custom field values.
404+ */
405+ export const CUSTOM_FIELD_ON_OBJECT_OUTPUT = {
406+ id : { type : 'string' , description : 'Custom field UUID' } ,
407+ title : { type : 'string' , description : 'Field title' } ,
408+ isPrivate : { type : 'boolean' , description : 'Whether the field is private' } ,
409+ valueLabel : {
410+ type : 'string' ,
411+ description :
412+ 'Human-readable value label, present only for ValueSelect and MultiValueSelect fields. MultiValueSelect returns an array of labels.' ,
413+ optional : true ,
414+ } ,
415+ value : { type : 'string' , description : 'Raw field value (type depends on fieldType)' } ,
416+ } as const satisfies Record < string , OutputProperty >
417+
376418export const CUSTOM_FIELDS_OUTPUT = {
377419 type : 'array' ,
378420 description : 'Custom field values' ,
379421 items : {
380422 type : 'object' ,
381- properties : {
382- id : { type : 'string' , description : 'Custom field UUID' } ,
383- title : { type : 'string' , description : 'Field title' } ,
384- isPrivate : { type : 'boolean' , description : 'Whether the field is private' } ,
385- valueLabel : { type : 'string' , description : 'Human-readable value label' , optional : true } ,
386- value : { type : 'string' , description : 'Raw field value (type depends on fieldType)' } ,
387- } ,
423+ properties : CUSTOM_FIELD_ON_OBJECT_OUTPUT ,
388424 } ,
389425} as const satisfies OutputProperty
390426
0 commit comments