Source: src/js/models/sysmeta/Replica.js

define([
  "common/XMLUtilities",
  "common/DateUtilities",
  "common/ValueUtilities",
  "common/ValidationUtilities",
  "models/sysmeta/SysMetaSchema",
], (
  XMLUtilities,
  DateUtilities,
  ValueUtilities,
  ValidationUtilities,
  SysMetaSchema,
) => {
  const { nullIfEmpty, normalizeText, isNonEmptyString } = ValueUtilities;
  const { normalizeReplicationStatus, REPLICATION_STATUSES } = SysMetaSchema;
  const { fixParsedValue, createValidationIssue } = ValidationUtilities;
  const REPLICA_CHILD_SEQUENCE = [
    { name: "replicaMemberNode", minOccurs: 1, maxOccurs: 1 },
    { name: "replicationStatus", minOccurs: 1, maxOccurs: 1 },
    { name: "replicaVerified", minOccurs: 1, maxOccurs: 1 },
  ];

  /**
   * Replica entry value object for System Metadata.
   * @class Replica
   * @classcategory Models/SysMeta
   * @since 2.39.0
   */
  class Replica {
    /**
     * Create a Replica instance.
     * @param {object} [data] Initial replica values.
     * @param {string|null} [data.replicaMemberNode] Replica member node
     * identifier.
     * @param {string|null} [data.replicationStatus] Replica replication status.
     * @param {string|Date|null} [data.replicaVerified] Replica verification
     * timestamp.
     */
    constructor(data = {}) {
      this.replicaMemberNode = normalizeText(data.replicaMemberNode);
      this.replicationStatus = normalizeReplicationStatus(
        data.replicationStatus,
      );
      this.replicaVerified =
        DateUtilities.toDate(nullIfEmpty(data.replicaVerified)) ||
        nullIfEmpty(data.replicaVerified);
    }

    /**
     * Create a Replica from a `replica` XML element.
     * @param {Element} element XML element to parse.
     * @returns {Replica} Parsed replica instance.
     */
    static fromElement(element) {
      const context = "Replica XML";
      XMLUtilities.requireAllowedAttributeNames(element, [], context);
      XMLUtilities.requireDirectChildSequence(
        element,
        REPLICA_CHILD_SEQUENCE,
        context,
      );

      return new Replica({
        replicaMemberNode: XMLUtilities.getDirectChildText(
          element,
          "replicaMemberNode",
        ),
        replicationStatus: XMLUtilities.getDirectChildText(
          element,
          "replicationStatus",
        ),
        replicaVerified: XMLUtilities.getDirectChildText(
          element,
          "replicaVerified",
        ),
      });
    }

    /**
     * Parse a `replica` XML element, dropping it when invalid.
     * Unknown child nodes and ordering issues are ignored silently.
     * @param {Element|null} element XML element to parse.
     * @param {Array<object>} [parseWarnings] Lossy parse warnings.
     * @param {string} [path] Field path used in warnings and validation.
     * @returns {Replica|null} Parsed replica or null when invalid.
     */
    static parse(element, parseWarnings = [], path = "replica") {
      if (!element) return null;

      const replica = new Replica({
        replicaMemberNode: XMLUtilities.getDirectChildText(
          element,
          "replicaMemberNode",
        ),
        replicationStatus: XMLUtilities.getDirectChildText(
          element,
          "replicationStatus",
        ),
        replicaVerified: XMLUtilities.getDirectChildText(
          element,
          "replicaVerified",
        ),
      });
      return fixParsedValue({
        value: replica,
        path,
        parseWarnings,
        invalidMessage:
          "Ignored invalid replica while parsing system metadata.",
        validate: (candidate) => candidate.validate(path),
        fallback: () => null,
      });
    }

    /**
     * Validate the replica state.
     * @param {string} [path] Base path used in validation errors. Defaults to
     * `replica`.
     * @returns {Array<object>} Validation errors for invalid fields.
     */
    validate(path = "replica") {
      const errors = [];

      if (!isNonEmptyString(this.replicaMemberNode)) {
        errors.push(
          createValidationIssue({
            field: `${path}.replicaMemberNode`,
            message:
              "replicaMemberNode is required and must be a non-empty string.",
          }),
        );
      }

      if (!isNonEmptyString(this.replicationStatus)) {
        errors.push(
          createValidationIssue({
            field: `${path}.replicationStatus`,
            message: "replicationStatus is required.",
          }),
        );
      } else if (
        !REPLICATION_STATUSES.includes(this.replicationStatus.toLowerCase())
      ) {
        errors.push(
          createValidationIssue({
            field: `${path}.replicationStatus`,
            message: `replicationStatus must be one of: ${REPLICATION_STATUSES.join(
              ", ",
            )}.`,
          }),
        );
      }

      if (!this.replicaVerified) {
        errors.push(
          createValidationIssue({
            field: `${path}.replicaVerified`,
            message: "replicaVerified is required.",
          }),
        );
      } else if (!DateUtilities.isValidDate(this.replicaVerified)) {
        errors.push(
          createValidationIssue({
            field: `${path}.replicaVerified`,
            message: "replicaVerified must be a valid date.",
          }),
        );
      }

      return errors;
    }

    /**
     * Serialize the replica to XML.
     * @param {XMLDocument} doc XML document used to create elements.
     * @returns {Element|null} Serialized XML element, or `null` when empty.
     */
    toElement(doc) {
      const element = doc.createElement("replica");
      XMLUtilities.appendTextElement(
        doc,
        element,
        "replicaMemberNode",
        this.replicaMemberNode,
      );
      XMLUtilities.appendTextElement(
        doc,
        element,
        "replicationStatus",
        this.replicationStatus,
      );
      XMLUtilities.appendTextElement(
        doc,
        element,
        "replicaVerified",
        DateUtilities.toXmlDateTimeString(this.replicaVerified) || null,
      );

      return element.childNodes.length ? element : null;
    }

    /**
     * Return a JSON-serializable representation of the replica.
     * @returns {object} Plain replica data.
     */
    toJSON() {
      const { replicaMemberNode, replicationStatus, replicaVerified } = this;
      return {
        replicaMemberNode,
        replicationStatus,
        replicaVerified: DateUtilities.toISOString(replicaVerified) || null,
      };
    }
  }

  return Replica;
});