v1

package
v0.2.0-rc.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrFlagNotFound = &APIError{
		Status:  http.StatusNotFound,
		Code:    "FLAG_NOT_FOUND",
		Message: "Flag not found",
	}
	ErrRuleNotFound = &APIError{
		Status:  http.StatusNotFound,
		Code:    "RULE_NOT_FOUND",
		Message: "Rule not found",
	}
	ErrEnvironmentNotFound = &APIError{
		Status:  http.StatusNotFound,
		Code:    "ENVIRONMENT_NOT_FOUND",
		Message: "Environment not found",
	}
	ErrEnvironmentKeyTaken = &APIError{
		Status:  http.StatusConflict,
		Code:    "ENVIRONMENT_KEY_TAKEN",
		Message: "Environment key already in use",
	}
	ErrEnvironmentInUse = &APIError{
		Status:  http.StatusConflict,
		Code:    "ENVIRONMENT_IN_USE",
		Message: "Environment is in use",
	}
	ErrDefaultEnvironment = &APIError{
		Status:  http.StatusUnprocessableEntity,
		Code:    "DEFAULT_ENVIRONMENT",
		Message: "Default environment cannot be deleted",
	}
	ErrContextNotFound = &APIError{
		Status:  http.StatusNotFound,
		Code:    "CONTEXT_NOT_FOUND",
		Message: "Context not found",
	}
	ErrContextNameTaken = &APIError{
		Status:  http.StatusConflict,
		Code:    "CONTEXT_NAME_TAKEN",
		Message: "Context name already in use",
	}
	ErrContextInUse = &APIError{
		Status:  http.StatusConflict,
		Code:    "CONTEXT_IN_USE",
		Message: "Context is referenced by flags",
	}
	ErrUnauthorized = &APIError{
		Status:  http.StatusUnauthorized,
		Code:    "UNAUTHORIZED",
		Message: "Authentication required",
	}
	ErrInvalidCredentials = &APIError{
		Status:  http.StatusUnauthorized,
		Code:    "INVALID_CREDENTIALS",
		Message: "Invalid email or password",
	}
	ErrForbidden = &APIError{
		Status:  http.StatusForbidden,
		Code:    "FORBIDDEN",
		Message: "Access denied",
	}
	ErrAPIKeyNotFound = &APIError{
		Status:  http.StatusNotFound,
		Code:    "API_KEY_NOT_FOUND",
		Message: "API key not found",
	}
	ErrAuthNotConfigured = &APIError{
		Status:  http.StatusServiceUnavailable,
		Code:    "AUTH_NOT_CONFIGURED",
		Message: "Auth is not configured",
	}
	ErrNotReady = &APIError{
		Status:  http.StatusServiceUnavailable,
		Code:    "NOT_READY",
		Message: "Service is not ready",
	}
	ErrInternal = &APIError{
		Status:  http.StatusInternalServerError,
		Code:    "INTERNAL_ERROR",
		Message: "Internal server error",
	}
)

Functions

func NewRouter

func NewRouter(h *Handlers) http.Handler

func WriteError

func WriteError(w http.ResponseWriter, err error)

Types

type APIError

type APIError struct {
	Status  int                    `json:"-"`
	Code    string                 `json:"code"`
	Message string                 `json:"message"`
	Details []core.ValidationIssue `json:"details,omitempty"`
}

func BadRequest

func BadRequest(message string) *APIError

func InvalidRequest

func InvalidRequest(message string) *APIError

func (*APIError) Error

func (e *APIError) Error() string

type APIKeyResponse

type APIKeyResponse struct {
	ID            string  `json:"id"`
	Name          string  `json:"name"`
	Description   string  `json:"description,omitempty"`
	Prefix        string  `json:"prefix"`
	EnvironmentID string  `json:"environment_id"`
	CreatedAt     string  `json:"created_at"`
	UpdatedAt     string  `json:"updated_at"`
	LastUsedAt    *string `json:"last_used_at,omitempty"`
	RevokedAt     *string `json:"revoked_at,omitempty"`
	CreatedBy     *string `json:"created_by,omitempty"`
	DeletedBy     *string `json:"deleted_by,omitempty"`
}

type APIKeysHandler

type APIKeysHandler struct {
	// contains filtered or unexported fields
}

func NewAPIKeysHandler

func NewAPIKeysHandler(s *service.AuthService) *APIKeysHandler

func (*APIKeysHandler) Create

func (h *APIKeysHandler) Create(w http.ResponseWriter, r *http.Request)

func (*APIKeysHandler) List

func (*APIKeysHandler) Register

func (h *APIKeysHandler) Register(mux *http.ServeMux)

func (*APIKeysHandler) Revoke

func (h *APIKeysHandler) Revoke(w http.ResponseWriter, r *http.Request)

type AuditEntryResponse

type AuditEntryResponse struct {
	Version    int           `json:"version"`
	Action     string        `json:"action"`
	ActorLabel *string       `json:"actor_label,omitempty"`
	Snapshot   *FlagResponse `json:"snapshot,omitempty"`
	CreatedAt  string        `json:"created_at"`
}

type AuthHandler

type AuthHandler struct {
	// contains filtered or unexported fields
}

func NewAuthHandler

func NewAuthHandler(s *service.AuthService) *AuthHandler

func (*AuthHandler) APIKeyMiddleware

func (h *AuthHandler) APIKeyMiddleware(next http.Handler) http.Handler

func (*AuthHandler) AdminMiddleware

func (h *AuthHandler) AdminMiddleware(next http.Handler) http.Handler

func (*AuthHandler) Callback

func (h *AuthHandler) Callback(w http.ResponseWriter, r *http.Request)

func (*AuthHandler) Login

func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request)

func (*AuthHandler) Logout

func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request)

func (*AuthHandler) Me

func (*AuthHandler) PasswordLogin

func (h *AuthHandler) PasswordLogin(w http.ResponseWriter, r *http.Request)

func (*AuthHandler) RegisterAPI

func (h *AuthHandler) RegisterAPI(mux *http.ServeMux)

func (*AuthHandler) RegisterPublic

func (h *AuthHandler) RegisterPublic(root *http.ServeMux)

type AuthMeResponse

type AuthMeResponse struct {
	AuthEnabled   bool          `json:"auth_enabled"`
	Mode          string        `json:"mode,omitempty"`
	Authenticated bool          `json:"authenticated"`
	User          *UserResponse `json:"user,omitempty"`
}

type Config

type Config struct {
	Port            uint16
	ReadTimeout     time.Duration
	WriteTimeout    time.Duration
	IdleTimeout     time.Duration
	ShutdownTimeout time.Duration
}

type ContextFieldDTO

type ContextFieldDTO struct {
	Path string `json:"path"`
	Type string `json:"type"`
}

type ContextFieldReferenceResponse

type ContextFieldReferenceResponse struct {
	Path      string `json:"path"`
	RuleCount int    `json:"rule_count"`
}

type ContextReferenceResponse

type ContextReferenceResponse struct {
	ContextID        string                          `json:"context_id"`
	EnvironmentID    string                          `json:"environment_id"`
	EnvironmentKey   string                          `json:"environment_key"`
	FlagKey          string                          `json:"flag_key"`
	RuleCount        int                             `json:"rule_count"`
	ReferencedFields []ContextFieldReferenceResponse `json:"referenced_fields"`
}

type ContextResponse

type ContextResponse struct {
	ID          string            `json:"id"`
	Name        string            `json:"name"`
	Description string            `json:"description,omitempty"`
	Fields      []ContextFieldDTO `json:"fields"`
	CreatedAt   string            `json:"created_at"`
	UpdatedAt   string            `json:"updated_at"`
	CreatedBy   *string           `json:"created_by,omitempty"`
	DeletedBy   *string           `json:"deleted_by,omitempty"`
}

type ContextsHandler

type ContextsHandler struct {
	// contains filtered or unexported fields
}

func NewContextsHandler

func NewContextsHandler(s *service.ContextService) *ContextsHandler

func (*ContextsHandler) CreateContext

func (h *ContextsHandler) CreateContext(w http.ResponseWriter, r *http.Request)

func (*ContextsHandler) DeleteContext

func (h *ContextsHandler) DeleteContext(w http.ResponseWriter, r *http.Request)

func (*ContextsHandler) GetContext

func (h *ContextsHandler) GetContext(w http.ResponseWriter, r *http.Request)

func (*ContextsHandler) ListContextReferences

func (h *ContextsHandler) ListContextReferences(w http.ResponseWriter, r *http.Request)

func (*ContextsHandler) ListContexts

func (h *ContextsHandler) ListContexts(w http.ResponseWriter, r *http.Request)

func (*ContextsHandler) Register

func (h *ContextsHandler) Register(mux *http.ServeMux)

func (*ContextsHandler) UpdateContext

func (h *ContextsHandler) UpdateContext(w http.ResponseWriter, r *http.Request)

type CreateAPIKeyRequest

type CreateAPIKeyRequest struct {
	Name          string `json:"name"`
	Description   string `json:"description"`
	EnvironmentID string `json:"environment_id"`
}

type CreateAPIKeyResponse

type CreateAPIKeyResponse struct {
	APIKeyResponse
	Token string `json:"token"`
}

type CreateContextRequest

type CreateContextRequest struct {
	Name        string            `json:"name"`
	Description string            `json:"description"`
	Fields      []ContextFieldDTO `json:"fields"`
}

type CreateEnvironmentRequest

type CreateEnvironmentRequest struct {
	Key         string `json:"key"`
	Name        string `json:"name"`
	Description string `json:"description"`
}

type CreateFlagRequest

type CreateFlagRequest struct {
	Key          string              `json:"key"`
	Description  string              `json:"description"`
	Type         string              `json:"type"`
	Enabled      bool                `json:"enabled"`
	Rules        []CreateRuleRequest `json:"rules"`
	DefaultValue json.RawMessage     `json:"default_value"`
	ContextID    *string             `json:"context_id,omitempty"`
}

type CreateRuleRequest

type CreateRuleRequest struct {
	ID          string          `json:"id,omitempty"`
	Description string          `json:"description"`
	Expression  string          `json:"expression"`
	Rollout     RolloutResponse `json:"rollout"`
	Value       json.RawMessage `json:"value"`
}

type EnvironmentResponse

type EnvironmentResponse struct {
	ID          string  `json:"id"`
	Key         string  `json:"key"`
	Name        string  `json:"name"`
	Description string  `json:"description,omitempty"`
	CreatedAt   string  `json:"created_at"`
	UpdatedAt   string  `json:"updated_at"`
	CreatedBy   *string `json:"created_by,omitempty"`
	DeletedBy   *string `json:"deleted_by,omitempty"`
}

type EnvironmentsHandler

type EnvironmentsHandler struct {
	// contains filtered or unexported fields
}

func NewEnvironmentsHandler

func NewEnvironmentsHandler(service *service.EnvironmentService) *EnvironmentsHandler

func (*EnvironmentsHandler) Create

func (*EnvironmentsHandler) Delete

func (*EnvironmentsHandler) Get

func (*EnvironmentsHandler) List

func (*EnvironmentsHandler) Register

func (h *EnvironmentsHandler) Register(mux *http.ServeMux)

func (*EnvironmentsHandler) Update

type EvalAllResponse

type EvalAllResponse struct {
	Flags map[string]EvalFlagValueResponse `json:"flags"`
}

type EvalBucketResponse

type EvalBucketResponse struct {
	BucketBy     string `json:"bucket_by"`
	BucketValue  string `json:"bucket_value,omitempty"`
	BucketNumber *int   `json:"bucket_number,omitempty"`
	Percentage   int    `json:"percentage"`
	InRollout    bool   `json:"in_rollout"`
	Missing      bool   `json:"missing"`
}

type EvalFlagValueResponse

type EvalFlagValueResponse struct {
	ValueType string `json:"value_type"`
	Value     any    `json:"value"`
}

type EvalHandler

type EvalHandler struct {
	// contains filtered or unexported fields
}

func NewEvalHandler

func NewEvalHandler(s *service.EvalService) *EvalHandler

func (*EvalHandler) Evaluate

func (h *EvalHandler) Evaluate(w http.ResponseWriter, r *http.Request)

func (*EvalHandler) EvaluateAll

func (h *EvalHandler) EvaluateAll(w http.ResponseWriter, r *http.Request)

func (*EvalHandler) EvaluateFlag

func (h *EvalHandler) EvaluateFlag(w http.ResponseWriter, r *http.Request)

func (*EvalHandler) GetDefinitions

func (h *EvalHandler) GetDefinitions(w http.ResponseWriter, r *http.Request)

func (*EvalHandler) Register

func (h *EvalHandler) Register(mux *http.ServeMux)

func (*EvalHandler) RegisterAdmin

func (h *EvalHandler) RegisterAdmin(mux *http.ServeMux)

type EvalMatchedRuleResponse

type EvalMatchedRuleResponse struct {
	ID         string `json:"id"`
	Index      int    `json:"index"`
	Expression string `json:"expression"`
	Value      any    `json:"value"`
}

type EvalRequest

type EvalRequest struct {
	Context map[string]any `json:"context"`
}

type EvalResponse

type EvalResponse struct {
	Key       string `json:"key"`
	ValueType string `json:"value_type"`
	Value     any    `json:"value"`
}

type EvalRuleResultResponse

type EvalRuleResultResponse struct {
	ID         string `json:"id"`
	Index      int    `json:"index"`
	Expression string `json:"expression"`
	Value      any    `json:"value"`
	Matched    bool   `json:"matched"`
	Error      string `json:"error,omitempty"`
}

type EvalTraceResponse

type EvalTraceResponse struct {
	Key          string                   `json:"key"`
	ValueType    string                   `json:"value_type"`
	Enabled      bool                     `json:"enabled"`
	DefaultValue any                      `json:"default_value"`
	Value        any                      `json:"value"`
	Reason       string                   `json:"reason"`
	Error        string                   `json:"error,omitempty"`
	MatchedRule  *EvalMatchedRuleResponse `json:"matched_rule,omitempty"`
	Bucket       *EvalBucketResponse      `json:"bucket,omitempty"`
	RuleResults  []EvalRuleResultResponse `json:"rule_results"`
}

type FlagResponse

type FlagResponse struct {
	Key          string         `json:"key"`
	Description  string         `json:"description,omitempty"`
	Type         string         `json:"type"`
	Enabled      bool           `json:"enabled"`
	Rules        []RuleResponse `json:"rules"`
	DefaultValue any            `json:"default_value"`
	ContextID    *string        `json:"context_id,omitempty"`
	CreatedAt    string         `json:"created_at"`
	UpdatedAt    string         `json:"updated_at"`
	CreatedBy    *string        `json:"created_by,omitempty"`
	UpdatedBy    *string        `json:"updated_by,omitempty"`
	DeletedBy    *string        `json:"deleted_by,omitempty"`
}

type FlagUsageBucketResponse

type FlagUsageBucketResponse struct {
	BucketStart   string  `json:"bucket_start"`
	FlagKey       string  `json:"flag_key,omitempty"`
	ValueType     string  `json:"value_type"`
	Value         any     `json:"value"`
	Reason        string  `json:"reason"`
	MatchedRuleID *string `json:"matched_rule_id,omitempty"`
	APIKeyID      *string `json:"api_key_id,omitempty"`
	APIKeyName    string  `json:"api_key_name,omitempty"`
	Source        string  `json:"source,omitempty"`
	Count         int64   `json:"count"`
}

type FlagUsageEventResponse

type FlagUsageEventResponse struct {
	ID            string         `json:"id"`
	ObservedAt    string         `json:"observed_at"`
	ValueType     string         `json:"value_type"`
	Value         any            `json:"value"`
	Reason        string         `json:"reason"`
	MatchedRuleID *string        `json:"matched_rule_id,omitempty"`
	APIKeyID      *string        `json:"api_key_id,omitempty"`
	Source        string         `json:"source,omitempty"`
	LatencyMs     float64        `json:"latency_ms"`
	Context       map[string]any `json:"context,omitempty"`
}

type FlagUsageLatencyBucketResponse

type FlagUsageLatencyBucketResponse struct {
	BucketStart  string  `json:"bucket_start"`
	FlagKey      string  `json:"flag_key,omitempty"`
	Source       string  `json:"source,omitempty"`
	Count        int64   `json:"count"`
	AvgLatencyMs float64 `json:"avg_latency_ms"`
	P95LatencyMs float64 `json:"p95_latency_ms"`
}

type FlagUsageResponse

type FlagUsageResponse struct {
	Buckets        []FlagUsageBucketResponse        `json:"buckets"`
	LatencyBuckets []FlagUsageLatencyBucketResponse `json:"latency_buckets"`
	Events         []FlagUsageEventResponse         `json:"events"`
}

type FlagsHandler

type FlagsHandler struct {
	// contains filtered or unexported fields
}

func NewFlagsHandler

func NewFlagsHandler(service *service.FlagService, environments *service.EnvironmentService) *FlagsHandler

func (*FlagsHandler) CreateFlag

func (h *FlagsHandler) CreateFlag(w http.ResponseWriter, r *http.Request)

func (*FlagsHandler) DeleteFlag

func (h *FlagsHandler) DeleteFlag(w http.ResponseWriter, r *http.Request)

func (*FlagsHandler) GetFlag

func (h *FlagsHandler) GetFlag(w http.ResponseWriter, r *http.Request)

func (*FlagsHandler) GetFlagHistory

func (h *FlagsHandler) GetFlagHistory(w http.ResponseWriter, r *http.Request)

func (*FlagsHandler) GetFlags

func (h *FlagsHandler) GetFlags(w http.ResponseWriter, r *http.Request)

func (*FlagsHandler) Register

func (h *FlagsHandler) Register(mux *http.ServeMux)

type Handlers

type Handlers struct {
	Health       *HealthHandler
	Flags        *FlagsHandler
	Rules        *RulesHandler
	Environments *EnvironmentsHandler
	Contexts     *ContextsHandler
	Eval         *EvalHandler
	Auth         *AuthHandler
	APIKeys      *APIKeysHandler
	Usage        *UsageHandler
}

type HealthHandler

type HealthHandler struct {
	// contains filtered or unexported fields
}

func NewHealthHandler

func NewHealthHandler(readiness ReadinessChecker) *HealthHandler

func (*HealthHandler) Check

func (h *HealthHandler) Check(w http.ResponseWriter, r *http.Request)

func (*HealthHandler) Ready

func (h *HealthHandler) Ready(w http.ResponseWriter, r *http.Request)

func (*HealthHandler) Register

func (h *HealthHandler) Register(mux *http.ServeMux)

type HealthResponse

type HealthResponse struct {
	Status string `json:"status"`
}

type PasswordLoginRequest

type PasswordLoginRequest struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

type ReadinessChecker

type ReadinessChecker interface {
	Ping(ctx context.Context) error
}

type ReorderRulesRequest

type ReorderRulesRequest struct {
	RuleIDs []string `json:"rule_ids"`
}

type ReportUsageEventRequest

type ReportUsageEventRequest struct {
	FlagKey       string         `json:"flag_key"`
	ValueType     string         `json:"value_type"`
	Value         any            `json:"value"`
	Reason        string         `json:"reason"`
	MatchedRuleID *string        `json:"matched_rule_id,omitempty"`
	Source        string         `json:"source,omitempty"`
	LatencyMs     float64        `json:"latency_ms,omitempty"`
	ObservedAt    string         `json:"observed_at,omitempty"`
	Context       map[string]any `json:"context,omitempty"`
}

type ReportUsageRequest

type ReportUsageRequest struct {
	Events []ReportUsageEventRequest `json:"events"`
}

type RolloutResponse

type RolloutResponse struct {
	Percentage int    `json:"percentage"`
	BucketBy   string `json:"bucket_by,omitempty"`
}

type RuleResponse

type RuleResponse struct {
	ID          string          `json:"id"`
	Description string          `json:"description,omitempty"`
	Expression  string          `json:"expression"`
	Rollout     RolloutResponse `json:"rollout"`
	Value       any             `json:"value"`
	CreatedAt   string          `json:"created_at"`
	UpdatedAt   string          `json:"updated_at"`
	CreatedBy   *string         `json:"created_by,omitempty"`
	UpdatedBy   *string         `json:"updated_by,omitempty"`
	DeletedBy   *string         `json:"deleted_by,omitempty"`
}

type RulesHandler

type RulesHandler struct {
	// contains filtered or unexported fields
}

func NewRulesHandler

func NewRulesHandler(service *service.RuleService, environments *service.EnvironmentService) *RulesHandler

func (*RulesHandler) CreateRule

func (h *RulesHandler) CreateRule(w http.ResponseWriter, r *http.Request)

func (*RulesHandler) DeleteRule

func (h *RulesHandler) DeleteRule(w http.ResponseWriter, r *http.Request)

func (*RulesHandler) GetRule

func (h *RulesHandler) GetRule(w http.ResponseWriter, r *http.Request)

func (*RulesHandler) ListRules

func (h *RulesHandler) ListRules(w http.ResponseWriter, r *http.Request)

func (*RulesHandler) Register

func (h *RulesHandler) Register(mux *http.ServeMux)

func (*RulesHandler) ReorderRules

func (h *RulesHandler) ReorderRules(w http.ResponseWriter, r *http.Request)

func (*RulesHandler) UpdateRule

func (h *RulesHandler) UpdateRule(w http.ResponseWriter, r *http.Request)

type Server

type Server struct {
	// contains filtered or unexported fields
}

func NewServer

func NewServer(cfg Config, flagSvc *service.FlagService, ruleSvc *service.RuleService, envSvc *service.EnvironmentService, ctxSvc *service.ContextService, evalSvc *service.EvalService, authSvc *service.AuthService, usageStore UsageStore, readiness ReadinessChecker, logger *slog.Logger) *Server

func (*Server) Start

func (s *Server) Start(ctx context.Context) error

type UpdateContextRequest

type UpdateContextRequest struct {
	Name        string            `json:"name"`
	Description string            `json:"description"`
	Fields      []ContextFieldDTO `json:"fields"`
}

type UpdateEnvironmentRequest

type UpdateEnvironmentRequest = CreateEnvironmentRequest

type UpdateRuleRequest

type UpdateRuleRequest struct {
	Description string          `json:"description"`
	Expression  string          `json:"expression"`
	Rollout     RolloutResponse `json:"rollout"`
	Value       json.RawMessage `json:"value"`
}

type UsageHandler

type UsageHandler struct {
	// contains filtered or unexported fields
}

func NewUsageHandler

func NewUsageHandler(store UsageStore) *UsageHandler

func (*UsageHandler) GetEnvironmentUsage

func (h *UsageHandler) GetEnvironmentUsage(w http.ResponseWriter, r *http.Request)

func (*UsageHandler) GetFlagUsage

func (h *UsageHandler) GetFlagUsage(w http.ResponseWriter, r *http.Request)

func (*UsageHandler) Register

func (h *UsageHandler) Register(mux *http.ServeMux)

func (*UsageHandler) RegisterEval

func (h *UsageHandler) RegisterEval(mux *http.ServeMux)

func (*UsageHandler) ReportUsage

func (h *UsageHandler) ReportUsage(w http.ResponseWriter, r *http.Request)

type UsageStore

type UsageStore interface {
	RecordFlagUsage(ctx context.Context, event core.FlagUsageEvent) error
	ListFlagUsageBuckets(ctx context.Context, query core.FlagUsageQuery) ([]*core.FlagUsageBucket, error)
	ListEnvironmentUsageBuckets(ctx context.Context, query core.FlagUsageQuery) ([]*core.FlagUsageBucket, error)
	ListFlagUsageLatencyBuckets(ctx context.Context, query core.FlagUsageQuery) ([]*core.FlagUsageLatencyBucket, error)
	ListEnvironmentUsageLatencyBuckets(ctx context.Context, query core.FlagUsageQuery) ([]*core.FlagUsageLatencyBucket, error)
	ListFlagEvaluationEvents(ctx context.Context, query core.FlagUsageQuery) ([]*core.FlagUsageEvent, error)
}

type UserResponse

type UserResponse struct {
	ID          string  `json:"id"`
	Email       string  `json:"email"`
	Name        string  `json:"name,omitempty"`
	Description string  `json:"description,omitempty"`
	Admin       bool    `json:"admin"`
	CreatedAt   string  `json:"created_at"`
	UpdatedAt   string  `json:"updated_at"`
	CreatedBy   *string `json:"created_by,omitempty"`
	DeletedBy   *string `json:"deleted_by,omitempty"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL