docs: function resolveRelations(attrNode, context) {
      if (attrNode.dataSources || attrNode.resource) {
          const parentResource = context.parentResource;
          let childResource = attrNode;
  
          context.errorContext = getErrorContext(!parentResource ? 'resource' : 'sub-resource', context);
  
          if (attrNode.resource) {
              if (!context.resourceConfigs[attrNode.resource]) {
                  throw new ImplementationError('Unknown resource "' + attrNode.resource + '"' + context.errorContext);
              }
              childResource = context.resourceConfigs[attrNode.resource].config;
          }
  
          if (parentResource) {
              if (!('parentKey' in attrNode)) throw new ImplementationError('Missing parentKey' + context.errorContext);
              if (!('childKey' in attrNode)) throw new ImplementationError('Missing childKey' + context.errorContext);
  
              if (attrNode.parentKey === null) {
                  attrNode.parentKey = _.cloneDeep(parentResource.primaryKey);
              }
              if (attrNode.childKey === null) {
                  attrNode.childKey = _.cloneDeep(childResource.primaryKey);
              }
  
              if (attrNode.joinVia) {
                  const joinViaDataSource = attrNode.dataSources[attrNode.joinVia];
                  if (attrNode.parentKey.length !== joinViaDataSource.joinParentKey.length) {
                      throw new ImplementationError(
                          'Composite key length of parentKey (' +
                              attrNode.parentKey.length +
                              ') does not match joinParentKey length (' +
                              joinViaDataSource.joinParentKey.length +
                              ') ' +
                              'of DataSource "' +
                              attrNode.joinVia +
                              '"' +
                              context.errorContext
                      );
                  }
                  if (attrNode.childKey.length !== joinViaDataSource.joinChildKey.length) {
                      throw new ImplementationError(
                          'Composite key length of childKey (' +
                              attrNode.childKey.length +
                              ') does not match joinChildKey length (' +
                              joinViaDataSource.joinChildKey.length +
                              ') ' +
                              'of DataSource "' +
                              attrNode.joinVia +
                              '"' +
                              context.errorContext
                      );
                  }
              } else if (attrNode.parentKey.length !== attrNode.childKey.length) {
                  throw new ImplementationError(
                      'Composite key length of parentKey (' +
                          attrNode.parentKey.length +
                          ') does not match childKey length (' +
                          attrNode.childKey.length +
                          ')' +
                          context.errorContext
                  );
              }
  
              const errorContext = context.errorContext;
              context.errorContext = ' in parentKey' + errorContext;
              attrNode.resolvedParentKey = resolveKey(
                  attrNode.parentKey,
                  parentResource,
                  {
                      allowMultiValued: true
                  },
                  context
              );
  
              context.errorContext = ' in childKey' + errorContext;
              attrNode.resolvedChildKey = resolveKey(
                  attrNode.childKey,
                  childResource,
                  {
                      neededDataSources: ['primary'],
                      allowMultiValued: false
                  },
                  context
              );
              context.errorContext = errorContext;
          }
  
          context.parentResource = attrNode;
      }
  
      if (attrNode.attributes) {
          Object.keys(attrNode.attributes).forEach(subAttrName => {
              const subAttrNode = attrNode.attributes[subAttrName];
              const subContext = Object.assign({}, context);
              subContext.attrPath = context.attrPath.concat([subAttrName]);
  
              resolveRelations(subAttrNode, subContext);
          });
      }
  }