Cloud Software Group, Inc. EBX®
Documentation > Developer Guide > EBX® Script > Usage
Navigation modeDocumentation > Developer Guide > EBX® Script > Usage

Scripted REST service

Introduction

A scripted REST service defines a REST endpoint in EBX® and its related script. You create and maintain these services in the Script IDE. After you compile and publish the service from the IDE, it becomes available to REST clients.

Each operation in a scripted REST service is implemented as an EBX® Script exported function that runs when the operation is called via an HTTP request. Exported functions return a rest_response value. The core.rest unit provides utilities to construct responses. Requests and responses only use JSON content.

Endpoint mapping and operationId

A scripted REST service defines one or more endpoints. Each endpoint has a URL path and one or more HTTP methods. For each method, you assign an Operation id which is interpreted as an OpenAPI operationId . This value identifies the exported EBX® Script function that runs when a client calls the endpoint with that method. The Operation id value must match the name of corresponding exported function exactly.

The URL path can include path parameters (appearing in braces) and query parameters. Parameters are used by the related function as follows:

Examples (names reflect operationId ):

OpenAPI (excerpt):

{
  "openapi": "3.0.1",
  "paths": {
    "/users": {
      "get":    { "operationId": "getUsers",    "responses": { "200": { "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/User" } } } } } } },
      "post":   { "operationId": "createUser",  "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } },
                   "responses": { "201": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } } } }
    },
    "/users/{id}": {
      "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "number" } } ],
      "get":    { "operationId": "getUser",     "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } } } },
      "delete": { "operationId": "deleteUser",  "responses": { "204": {} } }
    },
    "/display/{p1}/{p2}": {
      "parameters": [
        { "name": "p1", "in": "path", "required": true, "schema": { "type": "string" } },
        { "name": "p2", "in": "path", "required": true, "schema": { "type": "number" } }
      ],
      "get": { "operationId": "displayParameters",
        "parameters": [
          { "name": "message",  "in": "query", "required": true,  "schema": { "type": "string" } },
          { "name": "count",    "in": "query", "required": false, "schema": { "type": "number" } },
          { "name": "datetime", "in": "query", "required": false, "schema": { "type": "string", "format": "date-time" } },
          { "name": "date",     "in": "query", "required": false, "schema": { "type": "string", "format": "date" } },
          { "name": "time",     "in": "query", "required": false, "schema": { "type": "string", "format": "time" } }
        ],
        "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Query" } } } } }
      }
    }
  },
  "components": {
    "schemas": {
      "User": { "type": "object", "properties": { "id": { "type": "number" }, "firstName": { "type": "string" }, "lastName": { "type": "string" } } },
      "Query": { "type": "object", "properties": { "p1": { "type": "string" }, "p2": { "type": "number" }, "message": { "type": "string" }, "count": { "type": "number" }, "datetime": { "type": "string", "format": "date-time" }, "date": { "type": "string", "format": "date" }, "time": { "type": "string", "format": "time" } } },
      "Error": { "type": "object", "properties": { "message": { "type": "string" } } }
    }
  }
}

Equivalent exported methods:

uses core.rest as rest;
uses core.list as list;

export function getUsers(): rest_response
begin
  // Return a list of users
  return rest.responseOf(200, list.of<User>());
end

export function createUser(const user: User): rest_response
begin
  return rest.responseOf(201, user);
end

export function getUser(id: decimal): rest_response
begin
  // Return typed object or a typed error
  var err := instanceOf<Error>();
  err.message := 'Not found';
  return rest.responseOf(404, err);
end

export function deleteUser(const id: decimal): rest_response
begin
  return rest.emptyResponse(204);
end

export function displayParameters(
  p1: string,
  p2: decimal,
  message: string,
  count: decimal,
  datetime: datetime,
  date: date,
  time: time): rest_response
begin
  var q := instanceOf<Query>();
  q.p1 := p1;
  q.p2 := p2;
  q.message := message;
  q.count := count;
  q.datetime := datetime;
  q.date := date;
  q.time := time;
  return rest.responseOf(200, q);
end

Exported functions

In the context of a scripted REST service, exported functions are REST operation handlers. Each exported function must return a rest_response and can accept parameters for path, query, or body as defined by the service contract.

For a full example, see the combined OpenAPI + exported methods above.

Parameters: parsing and validation

Scripted REST services handle parameter parsing and validation automatically:

Producing responses

To produce responses from exported functions, use core.rest utilities:

Schemas, types, and OpenAPI

OpenAPI 3 describes operations and data shapes. In scripted REST services:

Examples:

// Typed body
export function updateUser(const user: User): rest_response
begin
  return rest.responseOf(200, user);
end

// List of strings
uses core.list as list;
uses core.json as json;
export function getTags(): rest_response
begin
  var tags := list.of('one', 'two');
  return rest.responseOf(200, json.copyOfStrings(tags));
end

When using OpenAPI-driven templates, object types and field names are derived from the component schemas. Keep the returned structure aligned with the declared types for clear compile-time validation.

Supported OpenAPI subset

Scripted REST services support a subset of OpenAPI 3.0 features:

Authentication and session

Configure scripted REST services to allow anonymous access or require authentication. Implement your handlers so they do not assume a user context unless required by the endpoint design, and return appropriate status codes when access should be restricted.

Example:

uses core.rest as rest;
uses core.list as list;
uses core.json as json;

export function getSessionDetails(): rest_response
begin
  var userId := '';
  if _ebx.anonymous then
    userId := 'anonymous';
  else
    userId := _ebx.session.userId;

  var content := list.of(json.ofString(userId));
  return rest.responseOf(200, content);
end

Transactions and data access

Use regular EBX® Script transaction and data APIs to read and modify EBX® data. The following example includes a typical pattern with a transaction block and a ref to carry the response across the block:

uses core.rest as rest;
uses core.data as db;
uses core.complex as cp;

// Model reference for dataset types used in generics below (replace with your own module/name)
references model "directory" from module "my-module" as dir;

export function updateUser(const user: User): rest_response
begin
  ref response: rest_response;

  execute transaction tr on db.findDataspace('rest-test') as system
  begin
    const directory := db.findDataset(tr.dataspace, 'directory');
    const table := db.findTable<dir.User>(directory);

    var pk := cp.primaryKeyOf<dir.User>();
    pk.id := user.id;

    var record := db.lookupRecordForUpdate(tr, table, pk);
    if isNull(record) then
    begin
      var error := instanceOf<Error>();
      error.message := 'User "' | user.id | '" not found';
      response := rest.responseOf(404, error);
    end
    else
    begin
      record.firstName := user.firstName;
      record.lastName := user.lastName;
      db.saveRecord(record);
      response := rest.responseOf(200, user);
    end
  end

  return response;
end

When the request should run with elevated privileges, use execute transaction …​ as system . Otherwise, omit as system to use the authenticated user context.

Error handling

Scripted REST services handle errors as follows:

Example:

var err := instanceOf<Error>();
err.message := 'User not found';
return rest.responseOf(404, err);

See also

Documentation > Developer Guide > EBX® Script > Usage