<commit_before>import {
  CREATE,
  DELETE,
  GET_LIST,
  GET_MANY_REFERENCE,
  GET_ONE,
  UPDATE,
} from 'react-admin';
import isPlainObject from 'lodash.isplainobject';
import { parseHydraDocumentation } from '@api-platform/api-doc-parser';
import fetchHydra from './fetchHydra';
import { resolveSchemaParameters } from './schemaAnalyzer';

class ReactAdminDocument {
  constructor(obj) {
    Object.assign(this, obj, {
      originId: obj.id,
      id: obj['@id'],
    });
  }

  /**
   * @return {string}
   */
  toString() {
    return `[object ${this.id}]`;
  }
}

/**
 * Local cache containing embedded documents.
 * It will be used to prevent useless extra HTTP query if the relation is displayed.
 *
 * @type {Map}
 */
const reactAdminDocumentsCache = new Map();

/**
 * Transforms a JSON-LD document to a react-admin compatible document.
 *
 * @param {Object} document
 * @param {bool} clone
 * @param {bool} addToCache
 * @param {bool} useEmbedded
 *
 * @return {ReactAdminDocument}
 */
export const transformJsonLdDocumentToReactAdminDocument = (
  document,
  clone = true,
  addToCache = true,
  useEmbedded = false,
) => {
  if (clone) {
    // deep clone documents
    document = JSON.parse(JSON.stringify(document));
  }

  // The main document is a JSON-LD document, convert it and store it in the cache
  if (document['@id']) {
    document = new ReactAdminDocument(document);
  }

  // Replace embedded objects by their IRIs, and store the object itself in the cache to reuse without issuing new HTTP requests.
  Object.keys(document).forEach((key) => {
    // to-one
    if (isPlainObject(document[key]) && document[key]['@id']) {
      if (addToCache) {
        reactAdminDocumentsCache[document[key]['@id']] =
          transformJsonLdDocumentToReactAdminDocument(
            document[key],
            false,
            false,
          );
      }
      document[key] = useEmbedded ? document[key] : document[key]['@id'];

      return;
    }

    // to-many
    if (
      Array.isArray(document[key]) &&
      document[key].length &&
      isPlainObject(document[key][0]) &&
      document[key][0]['@id']
    ) {
      document[key] = document[key].map((obj) => {
        if (addToCache) {
          reactAdminDocumentsCache[obj['@id']] =
            transformJsonLdDocumentToReactAdminDocument(obj, false, false);
        }

        return useEmbedded ? obj : obj['@id'];
      });
    }
  });

  return document;
};

/**
 * Maps react-admin queries to a Hydra powered REST API
 *
 * @see http://www.hydra-cg.com/
 *
 * @example
 * CREATE   => POST http://my.api.url/posts/123
 * DELETE   => DELETE http://my.api.url/posts/123
 * GET_LIST => GET http://my.api.url/posts
 * GET_MANY => GET http://my.api.url/posts/123, GET http://my.api.url/posts/456, GET http://my.api.url/posts/789
 * GET_ONE  => GET http://my.api.url/posts/123
 * UPDATE   => PUT http://my.api.url/posts/123
 */
export default (
  entrypoint,
  httpClient = fetchHydra,
  apiDocumentationParser = parseHydraDocumentation,
  useEmbedded = false, // remove this parameter for 3.0 (as true)
) => {
  /** @type {Api} */
  let apiSchema;

  /**
   * @param {Resource} resource
   * @param {Object} data
   *
   * @returns {Promise<Object>}
   */
  const convertReactAdminDataToHydraData = (resource, data = {}) => {
    const fieldData = [];
    resource.fields.forEach(({ name, reference, normalizeData }) => {
      if (!(name in data)) {
        return;
      }

      if (reference && data[name] === '') {
        data[name] = null;
        return;
      }

      if (undefined === normalizeData) {
        return;
      }

      fieldData[name] = normalizeData(data[name]);
    });

    const fieldDataKeys = Object.keys(fieldData);
    const fieldDataValues = Object.values(fieldData);

    return Promise.all(fieldDataValues).then((fieldData) => {
      const object = {};
      for (let i = 0; i < fieldDataKeys.length; i++) {
        object[fieldDataKeys[i]] = fieldData[i];
      }

      return { ...data, ...object };
    });
  };

  /**
   * @param {string} resource
   * @param {Object} data
   * @param {Object} extraInformation
   *
   * @returns {Promise}
   */
  const transformReactAdminDataToRequestBody = (
    resource,
    data,
    extraInformation,
  ) => {
    /** @type {Resource} */
    const apiResource = apiSchema.resources.find(
      ({ name }) => resource === name,
    );
    if (undefined === apiResource) {
      return Promise.resolve(data);
    }

    return convertReactAdminDataToHydraData(apiResource, data).then((data) => {
      const values = Object.values(data);
      const containFile = (element) =>
        isPlainObject(element) &&
        Object.values(element).some((value) => value instanceof File);

      if (
        !extraInformation.hasFileField &&
        !values.some((value) => containFile(value))
      ) {
        return JSON.stringify(data);
      }

      const body = new FormData();
      Object.entries(data).map(([key, value]) => {
        // React-Admin FileInput format is an object containing a file.
        if (containFile(value)) {
          return body.append(
            key,
            Object.values(value).find((value) => value instanceof File),
          );
        }
        if ('function' === typeof value.toJSON) {
          return body.append(key, value.toJSON());
        }
        if (isPlainObject(value) || Array.isArray(value)) {
          return body.append(key, JSON.stringify(value));
        }
        return body.append(key, value);
      });

      return body;
    });
  };

  /**
   * @param {string} type
   * @param {string} resource
   * @param {{
   *   id: ?string,
   *   data: ?Object,
   *   target: ?string,
   *   filter: ?Object,
   *   pagination: ?Object,
   *   sort: ?Object,
   *   searchParams: ?Object
   * }} params
   *
   * @returns {Object}
   */
  const convertReactAdminRequestToHydraRequest = (type, resource, params) => {
    const entrypointUrl = new URL(entrypoint, window.location.href);
    const collectionUrl = new URL(`${entrypoint}/${resource}`, entrypointUrl);
    const itemUrl = new URL(params.id, entrypointUrl);
    const searchParams = params.searchParams || {};
    for (const searchParamKey in searchParams) {
      if (!searchParams.hasOwnProperty(searchParamKey)) {
        continue;
      }
      collectionUrl.searchParams.set(
        searchParamKey,
        searchParams[searchParamKey],
      );
      itemUrl.searchParams.set(searchParamKey, searchParams[searchParamKey]);
    }
    const extraInformation = params.data?.extraInformation ?? {};
    delete params.data?.extraInformation;

    switch (type) {
      case CREATE:
        return transformReactAdminDataToRequestBody(
          resource,
          params.data,
          extraInformation,
        ).then((body) => ({
          options: {
            body,
            method: 'POST',
          },
          url: collectionUrl,<commit_msg>fix: do not use modern operators (#399)<commit_after>import {
  CREATE,
  DELETE,
  GET_LIST,
  GET_MANY_REFERENCE,
  GET_ONE,
  UPDATE,
} from 'react-admin';
import isPlainObject from 'lodash.isplainobject';
import { parseHydraDocumentation } from '@api-platform/api-doc-parser';
import fetchHydra from './fetchHydra';
import { resolveSchemaParameters } from './schemaAnalyzer';

class ReactAdminDocument {
  constructor(obj) {
    Object.assign(this, obj, {
      originId: obj.id,
      id: obj['@id'],
    });
  }

  /**
   * @return {string}
   */
  toString() {
    return `[object ${this.id}]`;
  }
}

/**
 * Local cache containing embedded documents.
 * It will be used to prevent useless extra HTTP query if the relation is displayed.
 *
 * @type {Map}
 */
const reactAdminDocumentsCache = new Map();

/**
 * Transforms a JSON-LD document to a react-admin compatible document.
 *
 * @param {Object} document
 * @param {bool} clone
 * @param {bool} addToCache
 * @param {bool} useEmbedded
 *
 * @return {ReactAdminDocument}
 */
export const transformJsonLdDocumentToReactAdminDocument = (
  document,
  clone = true,
  addToCache = true,
  useEmbedded = false,
) => {
  if (clone) {
    // deep clone documents
    document = JSON.parse(JSON.stringify(document));
  }

  // The main document is a JSON-LD document, convert it and store it in the cache
  if (document['@id']) {
    document = new ReactAdminDocument(document);
  }

  // Replace embedded objects by their IRIs, and store the object itself in the cache to reuse without issuing new HTTP requests.
  Object.keys(document).forEach((key) => {
    // to-one
    if (isPlainObject(document[key]) && document[key]['@id']) {
      if (addToCache) {
        reactAdminDocumentsCache[document[key]['@id']] =
          transformJsonLdDocumentToReactAdminDocument(
            document[key],
            false,
            false,
          );
      }
      document[key] = useEmbedded ? document[key] : document[key]['@id'];

      return;
    }

    // to-many
    if (
      Array.isArray(document[key]) &&
      document[key].length &&
      isPlainObject(document[key][0]) &&
      document[key][0]['@id']
    ) {
      document[key] = document[key].map((obj) => {
        if (addToCache) {
          reactAdminDocumentsCache[obj['@id']] =
            transformJsonLdDocumentToReactAdminDocument(obj, false, false);
        }

        return useEmbedded ? obj : obj['@id'];
      });
    }
  });

  return document;
};

/**
 * Maps react-admin queries to a Hydra powered REST API
 *
 * @see http://www.hydra-cg.com/
 *
 * @example
 * CREATE   => POST http://my.api.url/posts/123
 * DELETE   => DELETE http://my.api.url/posts/123
 * GET_LIST => GET http://my.api.url/posts
 * GET_MANY => GET http://my.api.url/posts/123, GET http://my.api.url/posts/456, GET http://my.api.url/posts/789
 * GET_ONE  => GET http://my.api.url/posts/123
 * UPDATE   => PUT http://my.api.url/posts/123
 */
export default (
  entrypoint,
  httpClient = fetchHydra,
  apiDocumentationParser = parseHydraDocumentation,
  useEmbedded = false, // remove this parameter for 3.0 (as true)
) => {
  /** @type {Api} */
  let apiSchema;

  /**
   * @param {Resource} resource
   * @param {Object} data
   *
   * @returns {Promise<Object>}
   */
  const convertReactAdminDataToHydraData = (resource, data = {}) => {
    const fieldData = [];
    resource.fields.forEach(({ name, reference, normalizeData }) => {
      if (!(name in data)) {
        return;
      }

      if (reference && data[name] === '') {
        data[name] = null;
        return;
      }

      if (undefined === normalizeData) {
        return;
      }

      fieldData[name] = normalizeData(data[name]);
    });

    const fieldDataKeys = Object.keys(fieldData);
    const fieldDataValues = Object.values(fieldData);

    return Promise.all(fieldDataValues).then((fieldData) => {
      const object = {};
      for (let i = 0; i < fieldDataKeys.length; i++) {
        object[fieldDataKeys[i]] = fieldData[i];
      }

      return { ...data, ...object };
    });
  };

  /**
   * @param {string} resource
   * @param {Object} data
   * @param {Object} extraInformation
   *
   * @returns {Promise}
   */
  const transformReactAdminDataToRequestBody = (
    resource,
    data,
    extraInformation,
  ) => {
    /** @type {Resource} */
    const apiResource = apiSchema.resources.find(
      ({ name }) => resource === name,
    );
    if (undefined === apiResource) {
      return Promise.resolve(data);
    }

    return convertReactAdminDataToHydraData(apiResource, data).then((data) => {
      const values = Object.values(data);
      const containFile = (element) =>
        isPlainObject(element) &&
        Object.values(element).some((value) => value instanceof File);

      if (
        !extraInformation.hasFileField &&
        !values.some((value) => containFile(value))
      ) {
        return JSON.stringify(data);
      }

      const body = new FormData();
      Object.entries(data).map(([key, value]) => {
        // React-Admin FileInput format is an object containing a file.
        if (containFile(value)) {
          return body.append(
            key,
            Object.values(value).find((value) => value instanceof File),
          );
        }
        if ('function' === typeof value.toJSON) {
          return body.append(key, value.toJSON());
        }
        if (isPlainObject(value) || Array.isArray(value)) {
          return body.append(key, JSON.stringify(value));
        }
        return body.append(key, value);
      });

      return body;
    });
  };

  /**
   * @param {string} type
   * @param {string} resource
   * @param {{
   *   id: ?string,
   *   data: ?Object,
   *   target: ?string,
   *   filter: ?Object,
   *   pagination: ?Object,
   *   sort: ?Object,
   *   searchParams: ?Object
   * }} params
   *
   * @returns {Object}
   */
  const convertReactAdminRequestToHydraRequest = (type, resource, params) => {
    const entrypointUrl = new URL(entrypoint, window.location.href);
    const collectionUrl = new URL(`${entrypoint}/${resource}`, entrypointUrl);
    const itemUrl = new URL(params.id, entrypointUrl);
    const searchParams = params.searchParams || {};
    for (const searchParamKey in searchParams) {
      if (!searchParams.hasOwnProperty(searchParamKey)) {
        continue;
      }
      collectionUrl.searchParams.set(
        searchParamKey,
        searchParams[searchParamKey],
      );
      itemUrl.searchParams.set(searchParamKey, searchParams[searchParamKey]);
    }
    let extraInformation = {};
    if (params.data && params.data.extraInformation) {
      extraInformation = params.data.extraInformation;
      delete params.data.extraInformation;
    }

    switch (type) {
      case CREATE:
        return transformReactAdminDataToRequestBody(
          resource,
          params.data,
          extraInformation,
        ).then((body) => ({
          options: {
            body,
            method: 'POST',
          },
          url: collectionUrl,
{'fraction_non_alphanumeric': 0.1418667810177244, 'fraction_numerical': 0.003930817610062893, 'mean_word_length': 2.554229108458217, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2196118', 'n_tokens_mistral': 4333, 'n_tokens_neox': 4224, 'n_words': 943}