docs: function processRequestOptions(req, attrNode, context) {
      if ('id' in req) {
          if (context.attrPath.length > 0) {
              throw new RequestError('ID option only allowed at root (in "' + context.attrPath.join('.') + '")');
          }
  
          // TODO: Cast req.id to defined type and handle composite primaryKey
  
          attrNode.many = false;
  
          context.resourceTree.filter = [
              [
                  {
                      attribute: context.resourceTree.primaryKey,
                      operator: 'equal',
                      value: req.id
                  }
              ]
          ];
      }
  
      if ('filter' in req) {
          let requestFilter = req.filter.map(andFilter =>
              andFilter.map(filter => {
                  let filteredAttrNode = attrNode;
                  const subResourceAttrNodes = [];
  
                  let subContext = Object.assign({}, context);
                  filter.attribute.forEach(subAttr => {
                      filteredAttrNode = getAttributeWithAuthCheck([subAttr], filteredAttrNode, subContext);
                      if (filteredAttrNode.dataSources) subResourceAttrNodes.push(filteredAttrNode);
                      subContext.attrPath = subContext.attrPath.concat([subAttr]);
                  });
  
                  if (!filteredAttrNode.filter) {
                      throw new RequestError(
                          [
                              'Can not filter by attribute "' + filter.attribute.join('.') + '" ',
                              context.attrPath.length > 0 ? '(in "' + context.attrPath.join('.') + '") ' : ''
                          ].join('')
                      );
                  }
                  if (filteredAttrNode.filter.indexOf(filter.operator) === -1) {
                      throw new RequestError(
                          [
                              'Can not filter by attribute "' + filter.attribute.join('.') + '" ',
                              context.attrPath.length > 0 ? '(in "' + context.attrPath.join('.') + '") ' : '',
                              'with "' + filter.operator + '" ',
                              '(allowed operators: ' + filteredAttrNode.filter.join(', ') + ')'
                          ].join('')
                      );
                  }
  
                  const resolvedFilter = {
                      attribute: filteredAttrNode.map.default,
                      operator: filter.operator,
                      value: filter.value
                  };
                  let parentFilter = resolvedFilter;
  
                  // resolve filter by sub-resources:
                  if (subResourceAttrNodes.length > 0) {
                      let subFilter;
                      if (attrNode.subFilters) {
                          attrNode.subFilters.forEach(_subFilter => {
                              if (filter.attribute.join('.') === _subFilter.attribute.join('.')) {
                                  subFilter = _subFilter;
                              }
                          });
                      }
  
                      if (!subFilter) {
                          throw new RequestError(
                              `Can not filter by sub-resource attribute "${filter.attribute.join('.')}"` +
                                  (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '')
                          );
                      }
  
                      if (subFilter.filter.indexOf(filter.operator) === -1) {
                          throw new RequestError(
                              `Can not filter by sub-resource attribute "${filter.attribute.join('.')}"` +
                                  (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '') +
                                  ` with "${filter.operator}" (allowed operators: ${subFilter.filter.join(', ')})`
                          );
                      }
  
                      if (subFilter.rewriteTo) {
                          const filterAttrNode = getAttribute(subFilter.rewriteTo, attrNode, context);
                          parentFilter.attribute = filterAttrNode.map.default;
                      } else {
                          let filterContext = context;
                          subResourceAttrNodes.forEach(subResourceAttrNode => {
                              let subFilterTree = {
                                  dataSources: subResourceAttrNode.dataSources,
                                  attributes: [],
                                  filter: null,
                                  parentKey: subResourceAttrNode.resolvedParentKey,
                                  childKey: subResourceAttrNode.resolvedChildKey
                              };
  
                              // select childKey from DataSource:
                              subResourceAttrNode.childKey.forEach(childKeyAttrPath => {
                                  const childKeyAttrNode = getAttribute(childKeyAttrPath, subResourceAttrNode, context);
                                  subFilterTree.attributes.push({
                                      dataSourceMap: childKeyAttrNode.map.default,
                                      attrNode: childKeyAttrNode,
                                      fromDataSource: '#current-primary'
                                  });
                              });
  
                              // modify parent-filter to sub-filter:
                              parentFilter.attribute = subResourceAttrNode.resolvedParentKey;
                              parentFilter.operator = 'equal';
                              delete parentFilter.value;
                              const previousParentFilter = parentFilter;
  
                              // ... then filter sub-resource by given attribute:
                              parentFilter = {
                                  attribute: filteredAttrNode.map.default,
                                  operator: filter.operator,
                                  value: filter.value
                              };
                              subFilterTree.filter = [[parentFilter]];
  
                              // handle m:n relations with join-table:
                              if (subResourceAttrNode.joinVia) {
                                  const joinViaDataSource = subResourceAttrNode.dataSources[subResourceAttrNode.joinVia];
  
                                  const resolvedJoinParentKey = {};
                                  resolvedJoinParentKey[subResourceAttrNode.joinVia] =
                                      joinViaDataSource.resolvedJoinParentKey;
                                  const resolvedJoinChildKey = {};
                                  resolvedJoinChildKey[subResourceAttrNode.joinVia] =
                                      joinViaDataSource.resolvedJoinChildKey;
  
                                  const joinSubFilterTree = {
                                      dataSources: subResourceAttrNode.dataSources,
                                      primaryName: subResourceAttrNode.joinVia,
                                      attributes: [],
                                      filter: [
                                          [
                                              {
                                                  attribute: resolvedJoinChildKey,
                                                  operator: 'equal',
                                                  valueFromSubFilter: 0
                                              }
                                          ]
                                      ],
                                      parentKey: subFilterTree.parentKey,
                                      childKey: resolvedJoinParentKey
                                  };
  
                                  subFilterTree.parentKey = resolvedJoinChildKey;
  
                                  // select joinParentKey from DataSource:
                                  joinViaDataSource.joinParentKey.forEach(joinParentKeyAttrPath => {
                                      const joinParentKeyAttrNode = getAttribute(
                                          joinParentKeyAttrPath,
                                          subResourceAttrNode,
                                          context
                                      );
                                      joinSubFilterTree.attributes.push({
                                          dataSourceMap: joinParentKeyAttrNode.map.default,
                                          attrNode: joinParentKeyAttrNode,
                                          fromDataSource: subResourceAttrNode.joinVia
                                      });
                                  });
  
                                  // link join-sub-filter in between:
                                  joinSubFilterTree.subFilters = [subFilterTree];
                                  subFilterTree = joinSubFilterTree;
                              }
  
                              // link sub-filter to parent:
                              if (!filterContext.resourceTree.subFilters) {
                                  filterContext.resourceTree.subFilters = [];
                              }
  
                              previousParentFilter.valueFromSubFilter = filterContext.resourceTree.subFilters.length;
                              filterContext.resourceTree.subFilters.push(subFilterTree);
  
                              // switch filter-context:
                              filterContext = Object.assign({}, filterContext);
                              filterContext.resourceTree = subFilterTree;
                          });
                      }
                  }
  
                  // TODO: Check type of values
  
                  return resolvedFilter;
              })
          );
  
          if (context.resourceTree.filter) {
              // generate cross product of OR filters:
              const combinedFilter = [];
              requestFilter.forEach(andFilter => {
                  context.resourceTree.filter.forEach(andFilter2 => {
                      combinedFilter.push(cloneDeep(andFilter).concat(cloneDeep(andFilter2)));
                  });
              });
              requestFilter = combinedFilter;
          }
  
          context.resourceTree.filter = requestFilter;
      }
  
      if ('search' in req) {
          context.resourceTree.search = req.search;
      }
  
      const order = req.order || attrNode.defaultOrder;
      if (order) {
          context.resourceTree.order = order.map(orderPart => {
              let orderedAttrNode = attrNode;
              let subContext = Object.assign({}, context);
              orderPart.attribute.forEach(subAttr => {
                  orderedAttrNode = getAttributeWithAuthCheck([subAttr], orderedAttrNode, subContext);
                  if (orderedAttrNode.dataSources) {
                      throw new RequestError(
                          `Can not order by sub-resource attribute "${orderPart.attribute.join('.')}"` +
                              (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '')
                      );
                  }
                  subContext.attrPath = subContext.attrPath.concat([subAttr]);
              });
  
              if (!orderedAttrNode.order) {
                  throw new RequestError(
                      [
                          'Attribute "' + orderPart.attribute.join('.') + '" ',
                          context.attrPath.length > 0 ? ' (in "' + context.attrPath.join('.') + '") ' : '',
                          'can not be ordered'
                      ].join('')
                  );
              }
              if (orderedAttrNode.order.indexOf(orderPart.direction) === -1) {
                  throw new RequestError(
                      [
                          'Attribute "' + orderPart.attribute.join('.') + '" ',
                          context.attrPath.length > 0 ? ' (in "' + context.attrPath.join('.') + '")' : '',
                          'can not be ordered "' +
                              orderPart.direction +
                              '" (allowed: ' +
                              orderedAttrNode.order.join(', ') +
                              ')'
                      ].join('')
                  );
              }
  
              orderPart = Object.assign({}, orderPart);
              orderPart.attribute = orderedAttrNode.map.default;
  
              return orderPart;
          });
      }
  
      let limit = null;
      if ('limit' in req) {
          if (!attrNode.many) {
              throw new RequestError(
                  'Invalid limit on a single resource' +
                      (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '')
              );
          }
          limit = req.limit;
      } else if (attrNode.many) {
          limit = attrNode.defaultLimit || attrNode.maxLimit || (context.isMainResource ? 10 : null);
      }
      if (attrNode.maxLimit) {
          if (limit === null || limit > attrNode.maxLimit) {
              throw new RequestError(
                  `Invalid limit ${limit !== null ? limit : 'unlimited'}, maxLimit is ${attrNode.maxLimit}` +
                      (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '')
              );
          }
      }
      if (limit !== null) {
          context.resourceTree.limit = limit;
          if (!context.isMainResource && context.isMany) {
              context.resourceTree.limitPerGroup = true;
          }
      }
  
      if ('page' in req) {
          if (!('limit' in req)) {
              throw new RequestError(
                  'Always specify a fixed limit when requesting page' +
                      (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '')
              );
          }
  
          context.resourceTree.page = req.page;
      }
  }