///|
/// Commit a previously validated append using revision and offset as the
/// compare-and-swap precondition. Validation happens before the record array is
/// touched, so every failure leaves the store unchanged.
pub fn MemoryStore::commit_append(
  self : MemoryStore,
  plan : AppendPlan,
  limits : TusLimits,
) -> Result[UploadRecord, TusError] {
  let index = match self.index_of(plan.identifier) {
    None => return Err(upload_not_found(plan.identifier))
    Some(index) => index
  }
  let current = self.records[index]
  if current.revision != plan.expected_revision ||
    current.offset != plan.old_offset {
    return Err(storage_conflict(plan.identifier))
  }
  if current.is_complete() {
    return Err(upload_complete(plan.identifier))
  }
  match validate_append_plan(current, plan, limits) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let next_revision = match
    checked_add_i64(current.revision, 1L, field_name="storage-revision") {
    Err(_) => return Err(store_revision_exhausted(plan.identifier))
    Ok(value) => value
  }
  let bytes = current.data.to_array()
  for byte in plan.body {
    bytes.push(byte)
  }
  let updated : UploadRecord = {
    identifier: current.identifier,
    offset: plan.new_offset,
    length: plan.resolved_length,
    metadata: current.metadata.copy(),
    revision: next_revision,
    lifecycle: lifecycle(plan.new_offset, plan.resolved_length),
    data: Bytes::from_array(bytes),
  }
  match validate_record(updated, limits) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  self.records[index] = updated
  Ok(clone_record(updated))
}

///|
pub fn validate_append_plan(
  current : UploadRecord,
  plan : AppendPlan,
  limits : TusLimits,
) -> Result[Unit, TusError] {
  if current.identifier != plan.identifier {
    return Err(storage_conflict(plan.identifier))
  }
  let computed_end = match
    checked_body_end(plan.old_offset, plan.body.length()) {
    Err(error) => return Err(error)
    Ok(value) => value
  }
  if computed_end != plan.new_offset {
    return Err(
      tus_error(
        InternalInvariant,
        "TUS_APPEND_PLAN_END_INVALID",
        "append plan new offset does not equal old offset plus body size",
        status=500,
        expected=Some(computed_end.to_string()),
        actual=Some(plan.new_offset.to_string()),
      ),
    )
  }
  match validate_patch_size(plan.body, limits) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  if plan.new_offset > limits.max_upload_size {
    return Err(upload_too_large(limits.max_upload_size, plan.new_offset))
  }
  match (current.length, plan.resolved_length) {
    (Known(before), Known(after)) if before == after => ()
    (Deferred, Deferred) => ()
    (Deferred, Known(after)) => {
      if after < plan.new_offset {
        return Err(
          tus_error(
            InternalInvariant,
            "TUS_APPEND_PLAN_LENGTH_INVALID",
            "resolved length is smaller than the new offset",
            status=500,
          ),
        )
      }
      match validate_declared_size(after, limits) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
    }
    _ =>
      return Err(
        tus_error(
          InternalInvariant,
          "TUS_APPEND_PLAN_LENGTH_CHANGED",
          "append plan changes an already declared length",
          status=500,
        ),
      )
  }
  let expected_complete = match plan.resolved_length {
    Known(total) => plan.new_offset == total
    Deferred => false
  }
  if expected_complete != plan.completes_upload {
    return Err(
      tus_error(
        InternalInvariant,
        "TUS_APPEND_PLAN_COMPLETION_INVALID",
        "append plan completion flag disagrees with offset and length",
        status=500,
      ),
    )
  }
  Ok(())
}

///|
fn store_revision_exhausted(identifier : String) -> TusError {
  tus_error(
    StorageFailure,
    "TUS_STORAGE_REVISION_EXHAUSTED",
    "storage revision counter cannot be incremented",
    status=507,
    actual=Some(identifier),
  )
}