`POST /build/publish` opened with three guard clauses and `POST /build/upload`
with eight, each one a `return render json: { error: ... }` — name present,
version present, name format, version format, file present, filename prefix,
size, digest. That is a validation layer written by hand, in a place where it
cannot be unit tested: exercising it needs a request.
`PublishInputDto` was already there and was a bare `Struct` with no rules at
all, so the controller carried them. It is an `ActiveModel::Model` now, with
the presence and platform-inclusion validations on it, and `UploadInputDto`
joins it with the name and version formats and the `<name>-<version>` filename
convention. Each controller reads:
return render json: { error: input.error_message }, status: :bad_request unless input.valid?
Size and digest keep their own explicit checks, because they are not the same
answer: 413 tells a caller to stop, 422 tells it to retry a truncated upload,
and a single error bag cannot say which. Every status code the endpoints
answered before, they answer now — build_publish_controller_spec and
build_uploads_controller_spec pin all of them, unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
38 lines
948 B
Ruby
38 lines
948 B
Ruby
module WarpEngine
|
|
class UploadInputDto
|
|
include ActiveModel::Model
|
|
|
|
NAME_FORMAT = /\A[A-Za-z0-9._-]+\z/
|
|
|
|
attr_accessor :name, :version, :file, :sha256
|
|
|
|
validates :name, format: { with: NAME_FORMAT }
|
|
validates :version, format: { with: NAME_FORMAT }
|
|
validate :file_present
|
|
validate :filename_prefixed
|
|
|
|
def filename
|
|
return nil unless file.respond_to?(:original_filename)
|
|
|
|
File.basename(file.original_filename.to_s)
|
|
end
|
|
|
|
def error_message = errors.full_messages.to_sentence
|
|
|
|
private
|
|
|
|
def file_present
|
|
return if file.respond_to?(:original_filename)
|
|
|
|
errors.add(:file, "not provided")
|
|
end
|
|
|
|
def filename_prefixed
|
|
return if filename.nil? || errors.include?(:name) || errors.include?(:version)
|
|
return if filename.start_with?("#{name}-#{version}.", "#{name}-#{version}-")
|
|
|
|
errors.add(:file, "name must be prefixed with #{name}-#{version}")
|
|
end
|
|
end
|
|
end
|