Repository: google/go-github
File: github/repos_releases.go (Go)

Code before:
	}

	req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType)
	if err != nil {
		return nil, nil, err
	}

	asset := new(ReleaseAsset)
	resp, err := s.client.Do(ctx, req, asset)
	if err != nil {
		return nil, resp, err
	}
	return asset, resp, nil
}

// UploadReleaseAssetFromRelease uploads an asset using the UploadURL that's embedded
// in a RepositoryRelease object.
//
// This is a convenience wrapper that extracts the release.UploadURL (which is usually
// templated like "https://uploads.github.com/.../assets{?name,label}") and uploads
// the provided file using the existing upload helpers.
//
// GitHub API docs: https://docs.github.com/rest/releases/assets#upload-a-release-asset
//
//meta:operation POST /repos/{owner}/{repo}/releases/{release_id}/assets
func (s *RepositoriesService) UploadReleaseAssetFromRelease(ctx context.Context, release *RepositoryRelease, opts *UploadOptions, file *os.File) (*ReleaseAsset, *Response, error) {
	if release == nil || release.UploadURL == nil {
		return nil, nil, errors.New("release UploadURL must be provided")
	}
	if file == nil {
		return nil, nil, errors.New("file must be provided")
	}

	// Extract upload URL.
	uploadURL := *release.UploadURL

	// If uploadURL contains a template, strip it (e.g. "{?name,label}").
	if idx := strings.Index(uploadURL, "{"); idx != -1 {
		uploadURL = uploadURL[:idx]
	}

	// If uploadURL is absolute (starts with http/https), parse and use only the path.
	if strings.HasPrefix(uploadURL, "http://") || strings.HasPrefix(uploadURL, "https://") {
		if uParsed, err := url.Parse(uploadURL); err == nil {
			uploadURL = uParsed.Path
		}
	}

	// Defensive: always remove any leading '/' so client gets a relative path.
	uploadURL = strings.TrimPrefix(uploadURL, "/")


Review comment (refactor):
Using a `file *os.File` here is unnecessarily restrictive.
Note that the method that this function calls (`NewUploadRequest`) takes:

```
func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string, opts ...RequestOption) (*http.Request, error) {
```

which allows uploading of data from a much more flexible range of sources.

Please adopt the same API in this helper method.

Code after:

	req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType)
	if err != nil {
		return nil, nil, err
	}

	asset := new(ReleaseAsset)
	resp, err := s.client.Do(ctx, req, asset)
	if err != nil {
		return nil, resp, err
	}
	return asset, resp, nil
}

// UploadReleaseAssetFromRelease uploads an asset using the UploadURL that's embedded
// in a RepositoryRelease object.
//
// This is a convenience wrapper that extracts the release.UploadURL (which is usually
// templated like "https://uploads.github.com/.../assets{?name,label}") and uploads
// the provided data (reader + size) using the existing upload helpers.
//
// GitHub API docs: https://docs.github.com/rest/releases/assets#upload-a-release-asset
//
//meta:operation POST /repos/{owner}/{repo}/releases/{release_id}/assets
func (s *RepositoriesService) UploadReleaseAssetFromRelease(
	ctx context.Context,
	release *RepositoryRelease,
	opts *UploadOptions,
	reader io.Reader,
	size int64,
) (*ReleaseAsset, *Response, error) {
	if release == nil || release.UploadURL == nil {
		return nil, nil, errors.New("release UploadURL must be provided")
	}
	if reader == nil {
		return nil, nil, errors.New("reader must be provided")
	}
	if size < 0 {
		return nil, nil, errors.New("size must be >= 0")
	}

	// Strip URI-template portion (e.g. "{?name,label}") if present.
	uploadURL := *release.UploadURL
	if idx := strings.Index(uploadURL, "{"); idx != -1 {
		uploadURL = uploadURL[:idx]
	}

	// If this is a *relative* URL (no scheme), normalize it by trimming a leading "/"
	// so it works with Client.BaseURL path prefixes (e.g. "/api-v3/").
	if !strings.HasPrefix(uploadURL, "http://") && !strings.HasPrefix(uploadURL, "https://") {
		uploadURL = strings.TrimPrefix(uploadURL, "/")