Skip to content

Return 400 Bad Request for missing body in RegisterArtifact#169

Open
Copilot wants to merge 4 commits intodevfrom
copilot/create-artifact-registration-endpoint
Open

Return 400 Bad Request for missing body in RegisterArtifact#169
Copilot wants to merge 4 commits intodevfrom
copilot/create-artifact-registration-endpoint

Conversation

Copy link
Contributor

Copilot AI commented Jan 22, 2026

RegisterArtifact treated missing request body as a generic error, resulting in HTTP 500. Now returns proper 400 Bad Request via dedicated response object.

Changes

  • OpenAPI spec: Added 400 response to POST /groups/{groupID}/artifacts
  • BadRequestError type: Implements both error and RegisterArtifactResponseObject interfaces, following pattern of existing 404/409 errors
  • Handler: Returns NewBadRequestError("request body is required") instead of fmt.Errorf()

Example

Before:

if request.Body == nil {
    return nil, fmt.Errorf("request body is required")  // → HTTP 500
}

After:

if request.Body == nil {
    return NewBadRequestError("request body is required"), nil  // → HTTP 400
}

Error types satisfy both Go error semantics and HTTP contract requirements, ensuring proper status codes without bubbling generic errors.

Original prompt

Let's create an artifact registration endpoint (POST) given a groupid under /groups/{groupId}/artifacts that takes a json body of an artifactid, displayName, git repository with nullable website and issues.

The user has attached the following files from their workspace:

  • openapi.yaml

Conversation Summary

TITLE

Create POST Endpoint for Artifact Registration under Group

USER INTENT

The user wants to create a new REST API endpoint that allows registering artifacts within a specific group via a POST request.

TASK DESCRIPTION

Create a POST endpoint at /groups/{groupId}/artifacts that:

  • Accepts a JSON request body containing artifact registration details
  • Required fields: artifactId, displayName, git repository URL
  • Optional (nullable) fields: website URL, issues URL
  • Registers the artifact under the specified groupId

EXISTING

No prior work or existing implementation has been mentioned in this conversation.

PENDING

  • Define the request DTO/model for the JSON body with fields:
    • artifactId (required, string)
    • displayName (required, string)
    • git (required, string - repository URL)
    • website (optional/nullable, string - website URL)
    • issues (optional/nullable, string - issues/tracker URL)
  • Implement POST endpoint handler at /groups/{groupId}/artifacts
  • Add path parameter handling for groupId
  • Implement request validation
  • Implement artifact registration business logic
  • Add appropriate response handling (success/error cases)
  • Consider authentication/authorization requirements
  • Add appropriate HTTP status codes (e.g., 201 Created for success)

CODE STATE

No files have been discussed or modified yet.

RELEVANT CODE/DOCUMENTATION SNIPPETS

None provided in the conversation.

OTHER NOTES

  • The endpoint follows RESTful conventions with a nested resource pattern (artifacts under groups)
  • The nullable fields (website, issues) suggest these are supplementary metadata for the artifact
  • The artifact appears to be associated with source code (git repository) and may represent a software package or library registration system

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Create POST endpoint for artifact registration under group Add POST endpoint for artifact registration under groups Jan 22, 2026
Copilot AI requested a review from gabizou January 22, 2026 00:22
@gabizou
Copy link
Member

gabizou commented Jan 22, 2026

@copilot internal/app/service.go:89 - Should be a 404 error from the api contract that if the group doesn't exist, return a 404

openapi.yaml:440-444 - should likely be an array of accepted repositories

Copy link
Contributor Author

Copilot AI commented Jan 22, 2026

@copilot internal/app/service.go:89 - Should be a 404 error from the api contract that if the group doesn't exist, return a 404

...

Fixed both issues in commit aa7173d:

  1. Added ErrGroupNotFound error that's returned when the group doesn't exist (line 89 in service.go). This should be mapped to 404 in the HTTP layer.

  2. Changed gitRepository from a single string to an array in both the Artifact and ArtifactRegistration OpenAPI schemas (lines 402-407 and 440-445). The domain model and all handlers now use GitRepositories []string.

Copilot AI requested a review from gabizou January 22, 2026 17:43
@gabizou gabizou marked this pull request as ready for review January 28, 2026 05:21
Copilot AI review requested due to automatic review settings January 28, 2026 05:21
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for registering artifacts under a group via a new POST /groups/{groupID}/artifacts endpoint, including OpenAPI/spec updates and supporting service/handler logic.

Changes:

  • Added OpenAPI definition for artifact registration plus ArtifactRegistration schema; updated gitRepository to an array of URLs.
  • Implemented Service.RegisterArtifact and Handler.RegisterArtifact wiring, plus updated mocks/tests.
  • Regenerated api/api.gen.go to include the new endpoint/types; updated .gitignore for Go build outputs.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
openapi.yaml Defines the new POST endpoint and request schema; updates artifact git repository field to an array.
internal/httpapi/handler.go Adds strict handler implementation for RegisterArtifact.
internal/httpapi/handler_test.go Extends handler test scaffolding and adds a RegisterArtifact test.
internal/domain/group.go Introduces a domain Artifact model used by the service/handler.
internal/app/service.go Adds transactional RegisterArtifact implementation and new sentinel errors.
internal/app/service_test.go Adds unit tests for RegisterArtifact service behavior.
api/api.gen.go Regenerates OpenAPI bindings for the new endpoint and updated artifact schema.
.gitignore Adds ignores for Go binaries (with a duplicated bin/ entry).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

content:
application/json:
schema:
$ref: '#/components/schemas/Artifact'
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The POST /groups/{groupID}/artifacts operation only defines a 201 response, but the PR/API contract says group-not-found should be a 404 (and duplicates are prevented, which typically maps to 409). Add the missing error responses to the OpenAPI spec so the generated server/client types can return the correct HTTP status codes instead of falling back to generic error handling.

Suggested change
$ref: '#/components/schemas/Artifact'
$ref: '#/components/schemas/Artifact'
'404':
description: Group not found
'409':
description: Artifact already exists in the group

Copilot uses AI. Check for mistakes.
Comment on lines +450 to +459
website:
type: string
format: URL
example: "https://spongepowered.org/"
description: The website URL (optional)
issues:
type: string
format: URL
example: "https://github.com/SpongePowered/Sponge/issues"
description: The issues/tracker URL (optional)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API description mentions website and issues are nullable, but the schema only allows strings (no nullable: true). If you intend to accept explicit JSON nulls (and document that behavior), mark these properties as nullable in OpenAPI 3.0; otherwise update the contract wording to “optional” instead of “nullable”.

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +106
// Check if artifact already exists
_, err = tx.GetArtifactByGroupAndId(ctx, db.GetArtifactByGroupAndIdParams{
GroupID: artifact.GroupID,
ArtifactID: artifact.ArtifactID,
})
if err == nil {
// Artifact exists
return ErrArtifactAlreadyExists
}
if !errors.Is(err, pgx.ErrNoRows) {
return fmt.Errorf("failed to check if artifact exists: %w", err)
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate prevention isn’t actually guaranteed here: CreateArtifact is defined as an upsert (ON CONFLICT ... DO UPDATE, see internal/db/query.sql.go:12-20). Under concurrency, two requests can both pass the pre-check and the second will silently update the existing row, returning 201 instead of ErrArtifactAlreadyExists. Fix by making the insert fail on conflict (or DO NOTHING + detect no row returned) and map the unique-violation/conflict to ErrArtifactAlreadyExists.

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +113
// Create git repositories JSON array
gitRepos := artifact.GitRepositories
gitReposJSON, err := json.Marshal(gitRepos)
if err != nil {
return fmt.Errorf("failed to marshal git repositories: %w", err)
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json.Marshal on a nil []string returns the JSON literal null, not []. Since git_repositories is modeled as a JSONB array (db/schema.sql:13), storing null is likely incorrect and can break consumers that assume an array. Ensure artifact.GitRepositories is non-nil (and ideally non-empty) before marshaling, or normalize nil to an empty slice.

Copilot uses AI. Check for mistakes.
Comment on lines +115 to +122
// Create the artifact
_, err = tx.CreateArtifact(ctx, db.CreateArtifactParams{
GroupID: artifact.GroupID,
ArtifactID: artifact.ArtifactID,
Name: artifact.DisplayName,
Website: artifact.Website,
GitRepositories: gitReposJSON,
})
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

domain.Artifact includes Issues, and the handler returns issues in the 201 response, but the service/database write path never persists it (artifacts table has no issues column per db/schema.sql). This will make POST appear to succeed while the data can’t be retrieved later. Either persist issues in storage (schema + queries + CreateArtifactParams) or remove it from the registration flow/response until it’s supported.

Copilot uses AI. Check for mistakes.
Comment on lines +321 to +326
txMock.EXPECT().CreateArtifact(mock.Anything, mock.MatchedBy(func(params db.CreateArtifactParams) bool {
return params.GroupID == "org.example" &&
params.ArtifactID == "myartifact" &&
params.Name == "My Artifact" &&
params.Website != nil && *params.Website == "https://example.org"
})).Return(db.Artifact{}, nil)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CreateArtifact expectations don’t currently assert that GitRepositories is marshaled/passed correctly (e.g., JSON array bytes, and multi-repo ordering). Since this is a key behavior change, strengthen the matcher to validate params.GitRepositories decodes to the expected []string so regressions (including nil->null) are caught.

Copilot uses AI. Check for mistakes.
@gabizou gabizou force-pushed the copilot/create-artifact-registration-endpoint branch from 5d9458a to bf1d298 Compare January 28, 2026 21:58
- Add 404 and 409 responses to OpenAPI spec for POST /groups/{groupID}/artifacts
- Create custom HTTP error types that implement both error interface and response interface
- GroupNotFoundError returns 404 when group doesn't exist
- ArtifactAlreadyExistsError returns 409 when artifact already exists in group
- Update handler to return appropriate error types based on service errors
- Add comprehensive tests for error handling and HTTP status codes
- Regenerated API code with new response types

Co-authored-by: gabizou <1203877+gabizou@users.noreply.github.com>
Copilot AI changed the title Add POST endpoint for artifact registration under groups Add 404/409 error responses to artifact registration endpoint Jan 28, 2026
- Add 400 response to OpenAPI spec for POST /groups/{groupID}/artifacts
- Create BadRequestError custom type that implements both error interface and response interface
- Update handler to return BadRequestError instead of generic error for missing body
- Add comprehensive tests for BadRequestError and missing body scenario
- Regenerated API code with new 400 response type

This prevents missing request body from returning HTTP 500 and properly returns 400 Bad Request instead.

Co-authored-by: gabizou <1203877+gabizou@users.noreply.github.com>
Copilot AI changed the title Add 404/409 error responses to artifact registration endpoint Return 400 Bad Request for missing body in RegisterArtifact Jan 29, 2026
@gabizou gabizou requested a review from Copilot January 29, 2026 02:39
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +904 to +909
var body RegisterArtifactJSONRequestBody
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err))
return
}
request.Body = &body
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated strict handler always attempts to JSON-decode the request body and returns early on decode errors (including an empty body → EOF). That means RegisterArtifact in internal/httpapi/handler.go will never see request.Body == nil for real HTTP requests, so the new BadRequestError("request body is required") path won’t be used to produce the 400 response described in the PR. If you want a dedicated 400 response object/message for missing/empty bodies, handle EOF/http.NoBody in the request binding layer (or via NewStrictHandlerWithOptions + a custom RequestErrorHandlerFunc) and translate it into the desired 400 response.

Copilot uses AI. Check for mistakes.
Comment on lines +409 to +412
type: array
items:
type: string
format: URL
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Artifact.gitRepository is changed from a single string URL to an array of URLs. This is a breaking API change for all endpoints returning Artifact (and changes the generated Go type). Please confirm this is intended and ensure any existing clients/handlers are updated; if only registration needs an array, consider keeping the response field stable or introducing a new field instead.

Suggested change
type: array
items:
type: string
format: URL
type: string
format: URL

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants