cancel
Showing results for 
Search instead for 
Did you mean: 

B1 Service Layer Script Engine extensions development issues

former_member390407
Contributor
0 Kudos

Dear Experts,

Developing SE extensions I often face errors I don't really understand why. I have examined all documentation I could have found and spent a lot of time googling, but some of points are still not clear for me. I have prepared several examples, so, I hope community (or even someone from SAP support or solution architects team) can clarify the reasons of that behaviour. All of this examples were tested on SAP B1 9.3 PL 03

Example 1:

That is probably the most weird issue for me. In all examples on the forum and in the documentation you can find the following row at the beginning of your script:

var ServiceLayerContext = require('ServiceLayerContext.js');

After you just need to instantiate this object and it works fine. And so does the following:

var slContext = require('ServiceLayerContext.js');

But if I call this variable "serviceLayerContext" I get the weird error after instantiating and calling a method "startTransaction()": Script error: call function 'POST' failure [TypeError: this.contextHandle.startTransaction is not a function].

Here is my code where this error is reproduced:

var http = require('HttpModule.js'); 
// Require with the name different from ServiceLayerContext
var serviceLayerContext = require('ServiceLayerContext.js');

function GET()
{
}

function DELETE() {
}

function PATCH() {
}

function POST()
{
    // Instantiate
    var slObj = new serviceLayerContext();

    // Start transaction
    slObj.startTransaction();
    // Rollback immediately
    if (slObj.isInTransaction()) slObj.rollbackTransaction();

    /* SET HTTP RESPONSE */
    http.response.setContentType(http.ContentType.APPLICATION_JSON);
    http.response.setStatus(http.HttpStatus.HTTP_OK);
    http.response.setContent({status: 'success'});
    http.response.send();
}

If I call this script I get the following response body:

{
    "error": {
        "code": 512,
        "message": {
            "lang": "en-us",
            "value": "Script error: call function 'POST' failure [TypeError: this.contextHandle.startTransaction is not a function]."
        }
    }
}

If I rename serviceLayerContext context to ServiceLayerContext (or slContext for example) everything goes smooth.

Is that expected behaviour or I do anything wrong?

Example 2:

In all examples on the forum and in the documentation only entry point functions are used. Is there a limitation to write additional function in uploaded JS files? Cause I face unexpected behaviour (sometimes it forces an error, sometimes not) if I write a function after method functions. Mostly this errors about compilation and unexpected tokens (I got errors about all tokens including ILLEGAL)

Here is a code example where issue is reproduced:

var http = require('HttpModule.js'); 

function GET() {
}

function PATCH() {
}

function DELETE() {
}

function POST() {
    // Test object to log
    var obj =
    {
	Property1: 1,
	Property2: true,
	Property3: 'Test',
	Property4: function (value) { return true; },
	Property5: function (value) { return true; },
	Property6: function (value) { return true; },
	Property7: function (value) { return true; },
	Property8: function (value) { return true; },
	Property9: function (value) { return true; },
	Property10: function (value) { return true; },
	Property11: function (value) { return true; },
	Property12: function (value) { return true; },
	Property13: function (value) { return true; }
    };
    
    logObject('Test', obj);
	
    /* SET HTTP RESPONSE */
    http.response.setContentType(http.ContentType.APPLICATION_JSON);
    http.response.setStatus(http.HttpStatus.HTTP_OK);
    http.response.setContent({result: 'success'});
    http.response.send();
}

// Logs object in log files. If property is a function - logs it as 'Func'
function logObject(objName, obj)
{
	var replacer = function (key, value)
	{
		// Get 'Func' value if the property is a function
		if (typeof value === 'function')
		{
			return 'Func';  
		}
		return value;
	};


	var stringToLog = '';
	try
	{
		stringToLog = JSON.stringify(obj, replacer, 2);
	}
	catch (exc)
	{
		stringToLog = 'Cannot serialize object';
	}


	// Log string
	console.log(objName + ': ' + stringToLog);
}


If I call this script I get the following response:

{
    "error": {
        "code": 511,
        "message": {
            "lang": "en-us",
            "value": "Script error: compile error [SyntaxError: Unexpected identifier]."
        }
    }
}

If I move logic of the function inside POST function everything goes smooth.

Example 3:

Probably this error is about functions and variables out of entry points but I was really surprised with the results. Here is my script text:

// Require necessary entities
// Required to handle http calls
var http = require('HttpModule.js'); 
// Required to call SL entities/actions
var ServiceLayerContext = require('ServiceLayerContext.js');
// Entity proxy objects
var Customer = require('EntityType/BusinessPartner');

// Result Object
var result = { 
	IsSuccess: true,
	ErrorMsg: '',
	BookingSapCode: '',
	CustomerSapCode: ''
};

// Validator for an input object
var inputValidator = 
{
	BookingId: function (value) { return true; },
	BookingSapCode: function (value) { return true; },
	IsCanceled: function (value) { return true; },
	Date: function (value) { return isNaN(Date.parse(value)); },
	Email: function (value) { return true; },
	Phone: function (value) { return true; },
	CustomerId: function (value) { return true; },
	Title: function (value) { return true; },
	Name: function (value) { return true; },
	Surname: function (value) { return true; },
	CustomerSapCode: function (value) { return true; },
	CustomerSapSeries: function (value) { return true; },
	Details: function (value)
	{
		// Check if it's an array
		if (!Array.isArray(value)) return false;

		// Instantiate SL Context
		var slContext = new ServiceLayerContext();

		// For each ItemCode check if this item exists
		value.forEach(function (element)
		{
			if (!checkItemCode(element.SapItemCode, slContext)) return false;
		});

		return true;
	}
};

// ====================================	ENTRY POINTS ================================================== //

function GET() {

}

function DELETE() {

}


function PATCH() {

}


function POST() {
	console.log('Execution started');

	// Get the body of the request
	logObject('val', inputValidator);
	var inputObject = getBody(inputValidator);
	logObject('inputObj', inputObject); // Log input object to check data

    /* SET HTTP RESPONSE */
    http.response.setContentType(http.ContentType.APPLICATION_JSON);
    http.response.setStatus(http.HttpStatus.HTTP_OK);
    http.response.setContent(result);
    http.response.send();
}


// ====================================	PRIVATE FUNCTIONS ================================================== //


function checkItemCode(itemCode, slContext)
{
	return true;
}


// ====================================	COMMON FUNCTIONS ================================================== //


// Gets body and validates structure via 'validator' passed. 
// Validator presents all properties as validation functions
function getBody(validator)
{
	// Try to get data from the body
	var jsonObj = http.request.getJsonObj();
	if (!jsonObj) 
	{
        throw http.ScriptException(http.HttpStatus.HTTP_BAD_REQUEST, "Fail to get the content of JSON format from the request payload");
	}


	// Only if there is the variable
    if (typeof validator !== 'undefined')
    {
    	var errors = validateObject(jsonObj, validator);
		logObject('errors', errors);
    	if (errors.length > 0)
    	{
			throw http.ScriptException(http.HttpStatus.HTTP_BAD_REQUEST, "Input object is invalid: " + errors.join(' | '));
    	}
    }


	// Return the deserialized body
	return jsonObj;
}


// Validates object by it's schema
function validateObject(object, schema) {
	var errors = Object.keys(schema)
		.map(function (property) 
		{
			var validator = {}; // Validation function for a property
			var isMissing = false;


			if (object[property] === undefined) // If object doesn't have this property - function always returns false
			{
				validator = function (value) { return false };
				isMissing = true;
			}
			else
			{
				validator = schema[property]; // Get validation function
			}


			return { property: property, isValid: validator(object[property]), isMissing: isMissing };
		})
		.reduce(function (errors, propertyCheck) 
		{
			if (propertyCheck.isValid === false) 
			{
				errors.push(propertyCheck.property + (propertyCheck.isMissing ? " is missing." : " is invalid."));
			}
    
			return errors;
		}, []);
	return errors;
}


// Logs object in {SL Installation Path}/logs/script/*
function logObject(name, obj)
{
	var replacer = (key, value) => {  
		// If we get a function, give us the code for that function  
		if (typeof value === 'function')
		{
			return 'Func';  
		}
		return value;
	};

	// Check if there is no serialization errors
	var stringToLog = '';
	try
	{
		stringToLog = name + ': ' + JSON.stringify(obj, replacer, 2);
	}
	catch (exc)
	{
		stringToLog = name + ': cannot be serialized - ' + exc;
	}

	// Log string
	console.log(stringToLog);
}

So, the idea is that I want to verify my request body. To do that I declare a blueprint object with methods instead of properties. This methods check the data of my request object.

But, I always get exceptions that some of fields are missing. Here is my reply:

{
    "error": {
        "code": 600,
        "message": {
            "lang": "en-us",
            "value": "Input object is invalid: CustomerSapSereries is missing."
        }
    }
}

But I don't have CustomerSapSereries property in my schema object. So, during the tests, when I changed some pieces of code (In my humble opinion unrelated to this issue) I got some of properties renamed (that was Titltle instead of Title and CustomerSerieses instead of CustomerSeries). That really confuses me and I would appreciate any clarifications of reasons of the following behaviour.

Update:

I guess that the problem with serviceLayerContext is because this variable is used in engine.

function getContextHandle(){
        return serviceLayerContext;
    }

Accepted Solutions (1)

Accepted Solutions (1)

former_member197733
Contributor
0 Kudos

Hi Sergey, thanks for the questions.

In the future, please split the problems in different questions. You might get quick answers that way 🙂

Example 1 - Reserved Names in the Script Engine

You already figure out the issue. For such (undocumented) cases, feel free to raise an incident with sap support. I will heads up our development to get this constraint documented.

Example 2: Additional Functions declaration

The engine expects one function per HTTP Method and that is it. That is why you are getting the error. You might be able trick the compiler with a function expression. Something like

var foo = function (bar) {return bar * 2}

And call it like

foo(2) // returns 4

Example 3: Odd error message

I don't know the case for this, apart from what was mentioned in the previous example. Could you handle some of those checks, like the "validobject" on your application? This would give you more control and easier troubleshooting.

former_member390407
Contributor
0 Kudos

Hi Ralph,

Thanks for the answer! I have some more questions regarding to your answers if you don't mind.

Example 1:

Thanks it was clarified a bit when I checked the existing modules. Is there a list of reserved variable names? I bet that it's not the only one that can raise an error. I would appreciate if you could share this list with community to prevent scripts unexpected behaviour.

Example 2:

Thanks I didn't find any pointers in documentation that it is the limitation. So in case I want to reuse some blocks of code, let's say between POST and PATCH, can I declare a function variable? Is that the only way? BTW, can I use arrow functions for this purposes? Like:

var foo = (bar) => {return bar * 2;}

Same stuff but I'm just not sure if it's officially supported.

Example 3:

That was just an example. I wanted to add this check on the server to make sure that JSON is correct to prevent any mistakes of changing the client side but forgetting about the server. That is just one case and if I can use variables but not functions I still can do that.

Many thanks in advance.

Answers (1)

Answers (1)

thiago
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hi Sergei,

Thanks for the nice and valuable feedback.
Please notice that 9.3 is officially released from PL04 onwards.
Consider upgrading your test environment.

Kind Regards,
Thiago.

former_member390407
Contributor
0 Kudos

Hi Thiago,

Thanks for the comment. I really appreciate SAP assistance in this question. Are there any changes regarding to my questions in this PL? I checked the changelog but didn't find anything relevant. I checked this document: https://help.sap.com/http.svc/rc/542eec5fd4454d988ce8fb0ebbcd8437/9.3/en-US/SAP_Business_One_93_TopR...