Files
Tosone f677ad3f5f docs: complete placeholder doc comments across the codebase
Replace every `// <Identifier> ...` placeholder doc comment with a real
description of the declared symbol. 276 comments in 71 files were rewritten:
149 top-level placeholders first, then a follow-up scan caught another 127
indented ones (interface methods and const blocks) that a column-anchored
match had missed.

Comments now open with the declared identifier and record the actual
contract - behaviour, side effects, error/panic conditions, units, HTTP
endpoints, lock keys and cache prefixes - instead of restating the
signature. Three placeholders carried a stale identifier and were corrected
to the declared symbol: ConfigurationDatabasePostgresql, GetImageRef and
Initialize.

No functional change: every modified line is a comment.

Verification:
- grep '^\s*//\s+\S+\s+\.\.\.\s*$' over all *.go returns no matches
- gofmt -l over the changed files prints nothing
- CGO_ENABLED=1 go build -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./... exits 0
- git diff HEAD -U0 filtered to non-comment changed lines is empty
2026-09-14 14:58:06 +08:00

307 lines
8.3 KiB
Go

// Copyright 2024 sigma
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base32"
"encoding/base64"
"encoding/json/v2"
"encoding/pem"
"fmt"
"log/slog"
"net/http"
"os"
"path"
"strconv"
"strings"
"sync"
"github.com/gin-gonic/gin"
"github.com/opencontainers/go-digest"
"github.com/go-sigma/sigma/pkg/api"
"github.com/go-sigma/sigma/pkg/consts"
"github.com/go-sigma/sigma/pkg/dal/models"
"github.com/go-sigma/sigma/pkg/server/errcode"
"github.com/go-sigma/sigma/pkg/utils/ptr"
)
// GetFromCtx reads key from the gin context and asserts it to T; it returns the
// zero value of T and false when the key is missing or holds a different type.
func GetFromCtx[T any](c *gin.Context, key string) (T, bool) {
val, exists := c.Get(key)
if !exists {
var zero T
return zero, false
}
v, ok := val.(T)
return v, ok
}
// GetContentLength returns the content length of the request.
func GetContentLength(req *http.Request) (int64, error) {
if req == nil {
return 0, fmt.Errorf("request is nil")
}
str := req.Header.Get(consts.HeaderContentLength)
if str == "" {
return 0, nil
}
length, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return 0, fmt.Errorf("content length is not a number")
}
return length, nil
}
// Scheme returns the request URL scheme, honoring the X-Forwarded-Proto header.
func Scheme(c *gin.Context) string {
if proto := c.GetHeader("X-Forwarded-Proto"); proto != "" {
return proto
}
if c.Request.TLS != nil {
return "https"
}
return "http"
}
// GenPathByDigest generates the path by the digest.
func GenPathByDigest(digest digest.Digest) string {
hex := digest.Hex()
return fmt.Sprintf("%s/%s/%s", hex[0:2], hex[2:4], hex[4:])
}
// GenBlobPathByDigest generates the blob storage path by the digest.
func GenBlobPathByDigest(digest digest.Digest) string {
return path.Join(consts.Blobs, GenPathByDigest(digest))
}
// GenManifestPathByDigest generates the manifest storage path by the digest.
func GenManifestPathByDigest(digest digest.Digest) string {
return path.Join(consts.Manifests, GenPathByDigest(digest))
}
// BindValidate binds and validates the request body. gin's ShouldBind
// already invokes the registered validator, so this is a thin wrapper.
func BindValidate(c *gin.Context, data any) error {
return c.ShouldBind(data)
}
// PanicIf panics if err is not nil
func PanicIf(err error) {
if err != nil {
panic(err)
}
}
// NormalizePagination normalizes the pagination
func NormalizePagination(in api.Pagination) api.Pagination {
if in.Page == nil || ptr.To(in.Page) < 1 {
in.Page = new(int(1))
}
if in.Limit == nil || ptr.To(in.Limit) > 100 || ptr.To(in.Limit) <= 0 {
in.Limit = new(int(10))
}
return in
}
// TrimHTTP strips a leading "http://" or "https://" scheme from in, or, when no
// scheme is present, a trailing "/".
func TrimHTTP(in string) string {
if after, ok := strings.CutPrefix(in, "http://"); ok {
return after
} else if after, ok := strings.CutPrefix(in, "https://"); ok {
return after
}
return strings.TrimSuffix(in, "/")
}
// IsDir returns true if given path is a directory,
// or returns false when it's a file or does not exist.
func IsDir(dir string) bool {
f, e := os.Stat(dir)
if e != nil {
return false
}
return f.IsDir()
}
// IsFile returns true if given path is a file,
// or returns false when it's a directory or does not exist.
func IsFile(filePath string) bool {
f, e := os.Stat(filePath)
if e != nil {
return false
}
return !f.IsDir()
}
// IsExist checks whether a file or directory exists.
// It returns false when the file or directory does not exist.
func IsExist(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}
// MustMarshal marshals the given object to bytes.
func MustMarshal(in any) []byte {
result, err := json.Marshal(in)
if err != nil {
panic(err)
}
return result
}
// Must get the result of return val
func Must[T any](res T, err error) T {
if err != nil {
panic(err)
}
return res
}
// DirWithSlash returns the dir with slash
func DirWithSlash(id string) string {
// judge if the string has two slashes
if len(strings.Split(id, "/")) == 3 {
return id
}
if len(id) > 2 {
if !strings.Contains(id, "/") {
return DirWithSlash(fmt.Sprintf("%s/%s", id[0:2], id[2:]))
}
// remove the str before the last slash
str := id[strings.LastIndex(id, "/")+1:]
if len(str) > 2 {
str = fmt.Sprintf("%s/%s", str[0:2], str[2:])
} else {
return fmt.Sprintf("%s/%s", strings.TrimSuffix(id[0:strings.LastIndex(id, "/")+1], "/"), str)
}
return DirWithSlash(fmt.Sprintf("%s/%s", strings.TrimSuffix(id[0:strings.LastIndex(id, "/")+1], "/"), str))
}
return id
}
type stringsJoin interface {
String() string
}
// StringsJoin joins the String() form of each element of strs with sep, returning
// an empty string for an empty slice.
func StringsJoin[T stringsJoin](strs []T, sep string) string {
if len(strs) == 0 {
return ""
}
if len(strs) == 1 {
return strs[0].String()
}
b := make([]string, len(strs))
for i, str := range strs {
b[i] = str.String()
}
return strings.Join(b, sep)
}
// UnwrapJoinedErrors flattens an errors.Join-style error into a single ": "-joined
// string, falling back to err.Error() when it does not implement Unwrap() []error.
func UnwrapJoinedErrors(err error) string {
e, ok := err.(interface{ Unwrap() []error })
if !ok {
return err.Error()
}
es := e.Unwrap()
var ss = make([]string, len(es))
for index, e := range es {
ss[index] = e.Error()
}
return strings.Join(ss, ": ")
}
// UserCtxErrorFormat is the response format used when the user is missing.
type UserCtxErrorFormat int
const (
// UserCtxErrorHTTP writes the regular API error response.
UserCtxErrorHTTP UserCtxErrorFormat = iota
// UserCtxErrorDistribution writes the OCI distribution-spec error response.
UserCtxErrorDistribution
)
// GetUserFromCtx returns the authenticated user from gin context.
// If the user is missing, it writes an unauthorized response and returns true.
func GetUserFromCtx(c *gin.Context, format UserCtxErrorFormat) (*models.User, bool) {
user, ok := GetFromCtx[*models.User](c, consts.ContextUser)
if !ok {
slog.Error("get user from context failed")
if format == UserCtxErrorDistribution {
errcode.NewDSError(c, errcode.DSErrCodeUnauthorized)
} else {
errcode.NewHTTPError(c, errcode.HTTPErrCodeUnauthorized)
}
return nil, true
}
return user, false
}
// OnceWithErr runs fn at most once via once and returns its error; a panic inside
// fn (or a second call racing the first) yields an error instead of propagating it.
func OnceWithErr(once *sync.Once, fn func() error) error {
var errChan = make(chan error, 1)
defer close(errChan)
once.Do(func() {
defer func() {
if r := recover(); r != nil {
errChan <- fmt.Errorf("%v", r)
}
}()
err := fn()
errChan <- err
})
return <-errChan
}
// GenRsaPriKey generates an RSA private key of 1024, 2048 or 4096 bits and returns
// it as a base64-encoded PKCS#1 PEM block; other lengths are rejected.
func GenRsaPriKey(length int) (string, error) {
if length != 1024 && length != 2048 && length != 4096 {
return "", fmt.Errorf("rsa length is not allow")
}
privateKey, err := rsa.GenerateKey(rand.Reader, length)
if err != nil {
return "", fmt.Errorf("generate rsa private key failed")
}
privateKeyPEM := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
}
return base64.StdEncoding.EncodeToString(pem.EncodeToMemory(privateKeyPEM)), nil
}
// GenSecureID returns a lowercase base32 (hex alphabet) encoding of n
// cryptographically random bytes; it panics if the system entropy source fails.
func GenSecureID(n int) string {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
panic(fmt.Sprintf("gen secure rand id failed: %v", err))
}
return strings.ToLower(base32.HexEncoding.EncodeToString(b))
}