import { CallExpressionShape, Plugin, type Record as FluentRecord } from '@servicenow/sdk-build-core' import { NowIdShape } from './now-id-plugin' // Fluent camelCase values → DB stored values type MsgAuthType = 'noAuthentication' | 'basic' | 'oauth2' type FnAuthType = 'inheritFromParent' | MsgAuthType // DB → Fluent: derive message-level auth type from record fields function determineMsgAuthType(record: FluentRecord): MsgAuthType { const authTypeRaw = record.get('authentication_type').ifString()?.getValue() ?? '' if (authTypeRaw === 'oauth2') { return 'oauth2' } if (authTypeRaw === 'no_authentication') { return 'noAuthentication' } // 'basic' is the modern choice; 'basic_simple' is the legacy value — both map to 'basic' if (authTypeRaw === 'basic' || authTypeRaw === 'basic_simple') { return 'basic' } return 'noAuthentication' } // DB → Fluent: derive function-level auth type from record fields function determineFnAuthType(record: FluentRecord): FnAuthType { const authTypeRaw = record.get('authentication_type').ifString()?.getValue() ?? '' if (authTypeRaw === 'oauth2') { return 'oauth2' } if (authTypeRaw === 'no_authentication') { return 'noAuthentication' } // 'basic' is the modern choice; 'basic_simple' is the legacy value — both map to 'basic' if (authTypeRaw === 'basic' || authTypeRaw === 'basic_simple') { return 'basic' } // 'inherit_from_parent' or empty/default means the function inherits from the parent message return 'inheritFromParent' } // Fluent → DB: translate camelCase auth type to the DB authentication_type choice value function toDbAuthType(authType: string): string { if (authType === 'oauth2') { return 'oauth2' } if (authType === 'noAuthentication') { return 'no_authentication' } if (authType === 'basic') { return 'basic' } if (authType === 'inheritFromParent') { return 'inherit_from_parent' } throw new Error(`Unknown authentication type: ${authType}`) } // DB → Fluent: translate DB escape type to camelCase function toFluentEscapeType(dbValue: string): string { if (dbValue === 'xml') { return 'escapeXml' } // 'string' (DB default) → 'noEscaping' return 'noEscaping' } // Fluent → DB: translate camelCase escape type to DB value function toDbEscapeType(escapeType: string): string { if (escapeType === 'escapeXml') { return 'xml' } // 'noEscaping' (Fluent default) → DB 'string' return 'string' } export const RestMessagePlugin = Plugin.create({ name: 'RestMessagePlugin', records: { sys_rest_message_fn: { coalesce: ['rest_message', 'function_name'], }, sys_rest_message: { relationships: { sys_rest_message_headers: { via: 'rest_message', descendant: true, }, sys_rest_message_fn: { via: 'rest_message', descendant: true, relationships: { sys_rest_message_fn_headers: { via: 'rest_message_function', descendant: true, }, sys_rest_message_fn_parameters: { via: 'rest_message_function', descendant: true, }, sys_rest_message_fn_param_defs: { via: 'rest_message_function', descendant: true, }, }, }, }, async toShape(record, { descendants }) { const messageHeaders = descendants.query('sys_rest_message_headers') const functions = descendants.query('sys_rest_message_fn') const fnHeaders = descendants.query('sys_rest_message_fn_headers') const fnVariables = descendants.query('sys_rest_message_fn_parameters') const fnQueryParams = descendants.query('sys_rest_message_fn_param_defs') // DB → Fluent auth type (camelCase) const msgAuthType = determineMsgAuthType(record) const functionShapes = functions.map((fn) => { const fnId = fn.getId().getValue() const thisFnHeaders = fnHeaders.filter((h) => { const ref = h.get('rest_message_function') return (ref.ifString()?.getValue() ?? ref.ifRecordId()?.getValue()) === fnId }) const thisVariables = fnVariables.filter((v) => { const ref = v.get('rest_message_function') return (ref.ifString()?.getValue() ?? ref.ifRecordId()?.getValue()) === fnId }) const thisQueryParams = fnQueryParams.filter((p) => { const ref = p.get('rest_message_function') return (ref.ifString()?.getValue() ?? ref.ifRecordId()?.getValue()) === fnId }) // DB → Fluent auth type (camelCase) const fnAuthType = determineFnAuthType(fn) const fnHasBasicProfile = !!fn.get('basic_auth_profile').ifString()?.ifNotEmpty() const fnHeaderShapes = thisFnHeaders.length > 0 ? thisFnHeaders.map((h) => h.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(h)), name: $, value: $, })) ) : undefined const fnVariableShapes = thisVariables.length > 0 ? thisVariables.map((v) => { // DB escape type ('xml' | 'string') → Fluent camelCase const dbEscapeType = v.get('type').ifString()?.getValue() ?? '' const escapeType = toFluentEscapeType(dbEscapeType) return v.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(v)), name: $, // Omit when it equals the default 'noEscaping' escapeType: $.val(escapeType).def('noEscaping'), })) }) : undefined const fnQueryParamShapes = thisQueryParams.length > 0 ? thisQueryParams.map((p) => p.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(p)), name: $, value: $.def(''), order: $.toNumber().def(0), })) ) : undefined return fn.transform(({ $ }) => ({ name: $.from('function_name'), // DB stores lowercase ('get', 'post') → Fluent uses uppercase ('GET', 'POST') httpMethod: $.from('http_method').map((v) => v.ifString()?.getValue()?.toUpperCase()), endpoint: $.from('rest_endpoint').def(''), content: $.def(''), midServer: $.from('use_mid_server').def(''), lock: $.from('lock').toBoolean().def(false), // Emit camelCase value; omit when it equals the default 'inheritFromParent' authenticationType: $.val(fnAuthType).def('inheritFromParent'), basicAuthProfile: fnHasBasicProfile ? $.from('basic_auth_profile') : undefined, oauthProfile: fnAuthType === 'oauth2' ? $.from('oauth2_profile') : undefined, headers: $.val(fnHeaderShapes), variables: $.val(fnVariableShapes), queryParams: $.val(fnQueryParamShapes), })) }) const msgHasBasicProfile = !!record.get('basic_auth_profile').ifString()?.ifNotEmpty() const msgHeaderShapes = messageHeaders.length > 0 ? messageHeaders.map((h) => h.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(h)), name: $, value: $, })) ) : undefined return { success: true, value: new CallExpressionShape({ source: record, callee: 'RestMessage', args: [ record.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(record)), name: $, endpoint: $.from('rest_endpoint'), description: $.def(''), // DB 'package_private' → Fluent 'packagePrivate' access: $.from('access') .map((v) => { const val = v.ifString()?.getValue() return val === 'package_private' ? 'packagePrivate' : val }) .def('packagePrivate'), // Emit camelCase value; omit when it equals the default 'noAuthentication' authenticationType: $.val(msgAuthType).def('noAuthentication'), basicAuthProfile: msgHasBasicProfile ? $.from('basic_auth_profile') : undefined, oauthProfile: msgAuthType === 'oauth2' ? $.from('oauth2_profile') : undefined, headers: $.val(msgHeaderShapes), functions: $.val(functionShapes.length > 0 ? functionShapes : undefined), })), ], }), } }, }, }, shapes: [ { shape: CallExpressionShape, fileTypes: ['fluent'], async toRecord(callExpression, { factory }) { if (callExpression.getCallee() !== 'RestMessage') { return { success: false } } const arg = callExpression.getArgument(0).asObject() const authType = arg.get('authenticationType').ifString()?.getValue() ?? 'noAuthentication' const isBasic = authType === 'basic' const isOauth2 = authType === 'oauth2' // Fluent 'packagePrivate' → DB 'package_private' const accessVal = arg.get('access').ifString()?.getValue() ?? 'packagePrivate' const accessXml = accessVal === 'packagePrivate' ? 'package_private' : accessVal const msgRecord = await factory.createRecord({ source: callExpression, table: 'sys_rest_message', explicitId: arg.get('$id'), properties: arg.transform(({ $ }) => ({ name: $, rest_endpoint: $.from('endpoint'), description: $.def(''), access: $.val(accessXml).def('package_private'), // Fluent camelCase → DB choice value: 'basic' | 'oauth2' | 'no_authentication' authentication_type: $.val(toDbAuthType(authType)).def('no_authentication'), // 'basic': store credential profile reference basic_auth_profile: isBasic ? $.from('basicAuthProfile') : $.val('').def(''), oauth2_profile: isOauth2 ? $.from('oauthProfile').def('') : $.val('').def(''), })), }) // Create message-level header records const headerElements = arg.get('headers').ifArray()?.getElements() ?? [] const headerRecords = await Promise.all( headerElements.map((h) => factory.createRecord({ source: h, table: 'sys_rest_message_headers', explicitId: h.asObject().get('$id'), properties: h.asObject().transform(({ $ }) => ({ rest_message: $.val(msgRecord.getId()), name: $, value: $, })), }) ) ) // Create function records with their child records const fnElements = arg.get('functions').ifArray()?.getElements() ?? [] const fnRecordsWithChildren = await Promise.all( fnElements.map(async (fnShape) => { const fn = fnShape.asObject() const fnAuthTypeVal = fn.get('authenticationType').ifString()?.getValue() const fnAuthType = fnAuthTypeVal ?? 'inheritFromParent' const fnIsBasic = fnAuthType === 'basic' const fnIsOauth2 = fnAuthType === 'oauth2' // All auth types translate via toDbAuthType — no special-casing const fnAuthTypeXml = toDbAuthType(fnAuthType) const fnRecord = await factory.createRecord({ source: fnShape, table: 'sys_rest_message_fn', properties: fn.transform(({ $ }) => ({ rest_message: $.val(msgRecord.getId()), function_name: $.from('name'), // Fluent uses uppercase ('GET', 'POST') → DB stores lowercase ('get', 'post') http_method: $.from('httpMethod').map((v) => v.ifString()?.getValue()?.toLowerCase()), rest_endpoint: $.from('endpoint').def(''), content: $.def(''), use_mid_server: $.from('midServer').def(''), lock: $.from('lock').def(false), // All auth types translate via toDbAuthType — no special-casing authentication_type: $.val(fnAuthTypeXml).def('inherit_from_parent'), basic_auth_profile: fnIsBasic ? $.from('basicAuthProfile') : $.val('').def(''), oauth2_profile: fnIsOauth2 ? $.from('oauthProfile').def('') : $.val('').def(''), })), }) // Create function-level header records const fnHeaderElements = fn.get('headers').ifArray()?.getElements() ?? [] const fnHeaderRecords = await Promise.all( fnHeaderElements.map((h) => factory.createRecord({ source: h, table: 'sys_rest_message_fn_headers', explicitId: h.asObject().get('$id'), properties: h.asObject().transform(({ $ }) => ({ rest_message_function: $.val(fnRecord.getId()), name: $, value: $, })), }) ) ) // Create variable substitution records (sys_rest_message_fn_parameters) const variableElements = fn.get('variables').ifArray()?.getElements() ?? [] const variableRecords = await Promise.all( variableElements.map((v) => { const escapeType = v.asObject().get('escapeType').ifString()?.getValue() ?? 'noEscaping' return factory.createRecord({ source: v, table: 'sys_rest_message_fn_parameters', explicitId: v.asObject().get('$id'), properties: v.asObject().transform(({ $ }) => ({ rest_message_function: $.val(fnRecord.getId()), name: $, // Fluent camelCase → DB: 'xml' | 'string' type: $.val(toDbEscapeType(escapeType)), })), }) }) ) // Create HTTP query parameter records (sys_rest_message_fn_param_defs) const queryParamElements = fn.get('queryParams').ifArray()?.getElements() ?? [] const queryParamRecords = await Promise.all( queryParamElements.map((p) => factory.createRecord({ source: p, table: 'sys_rest_message_fn_param_defs', explicitId: p.asObject().get('$id'), properties: p.asObject().transform(({ $ }) => ({ rest_message_function: $.val(fnRecord.getId()), name: $, value: $.def(''), order: $.def(0), })), }) ) ) return fnRecord.with(...fnHeaderRecords, ...variableRecords, ...queryParamRecords) }) ) return { success: true, value: msgRecord.with(...headerRecords, ...fnRecordsWithChildren), } }, }, ], })