mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-11 15:27:37 +00:00
all: use immutable node view in read path
This commit changes most of our (*)types.Node to types.NodeView, which is a readonly version of the underlying node ensuring that there is no mutations happening in the read path. Based on the migration, there didnt seem to be any, but the idea here is to prevent it in the future and simplify other new implementations. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:

committed by
Kristoffer Dalby

parent
5ba7120418
commit
73023c2ec3
@@ -1,3 +1,5 @@
|
||||
//go:generate go run tailscale.com/cmd/viewer --type=User,Node,PreAuthKey
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
|
@@ -18,6 +18,7 @@ import (
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -115,6 +116,15 @@ type Node struct {
|
||||
|
||||
type Nodes []*Node
|
||||
|
||||
func (ns Nodes) ViewSlice() views.Slice[NodeView] {
|
||||
vs := make([]NodeView, len(ns))
|
||||
for i, n := range ns {
|
||||
vs[i] = n.View()
|
||||
}
|
||||
|
||||
return views.SliceOf(vs)
|
||||
}
|
||||
|
||||
// GivenNameHasBeenChanged returns whether the `givenName` can be automatically changed based on the `Hostname` of the node.
|
||||
func (node *Node) GivenNameHasBeenChanged() bool {
|
||||
return node.GivenName == util.ConvertWithFQDNRules(node.Hostname)
|
||||
@@ -582,3 +592,185 @@ func (node Node) DebugString() string {
|
||||
sb.WriteString("\n")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (v NodeView) IPs() []netip.Addr {
|
||||
if !v.Valid() {
|
||||
return nil
|
||||
}
|
||||
return v.ж.IPs()
|
||||
}
|
||||
|
||||
func (v NodeView) InIPSet(set *netipx.IPSet) bool {
|
||||
if !v.Valid() {
|
||||
return false
|
||||
}
|
||||
return v.ж.InIPSet(set)
|
||||
}
|
||||
|
||||
func (v NodeView) CanAccess(matchers []matcher.Match, node2 NodeView) bool {
|
||||
if !v.Valid() || !node2.Valid() {
|
||||
return false
|
||||
}
|
||||
src := v.IPs()
|
||||
allowedIPs := node2.IPs()
|
||||
|
||||
for _, matcher := range matchers {
|
||||
if !matcher.SrcsContainsIPs(src...) {
|
||||
continue
|
||||
}
|
||||
|
||||
if matcher.DestsContainsIP(allowedIPs...) {
|
||||
return true
|
||||
}
|
||||
|
||||
if matcher.DestsOverlapsPrefixes(node2.SubnetRoutes()...) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (v NodeView) CanAccessRoute(matchers []matcher.Match, route netip.Prefix) bool {
|
||||
if !v.Valid() {
|
||||
return false
|
||||
}
|
||||
src := v.IPs()
|
||||
|
||||
for _, matcher := range matchers {
|
||||
if !matcher.SrcsContainsIPs(src...) {
|
||||
continue
|
||||
}
|
||||
|
||||
if matcher.DestsOverlapsPrefixes(route) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (v NodeView) AnnouncedRoutes() []netip.Prefix {
|
||||
if !v.Valid() {
|
||||
return nil
|
||||
}
|
||||
return v.ж.AnnouncedRoutes()
|
||||
}
|
||||
|
||||
func (v NodeView) SubnetRoutes() []netip.Prefix {
|
||||
if !v.Valid() {
|
||||
return nil
|
||||
}
|
||||
return v.ж.SubnetRoutes()
|
||||
}
|
||||
|
||||
func (v NodeView) AppendToIPSet(build *netipx.IPSetBuilder) {
|
||||
if !v.Valid() {
|
||||
return
|
||||
}
|
||||
v.ж.AppendToIPSet(build)
|
||||
}
|
||||
|
||||
func (v NodeView) RequestTagsSlice() views.Slice[string] {
|
||||
if !v.Valid() || !v.Hostinfo().Valid() {
|
||||
return views.Slice[string]{}
|
||||
}
|
||||
return v.Hostinfo().RequestTags()
|
||||
}
|
||||
|
||||
func (v NodeView) Tags() []string {
|
||||
if !v.Valid() {
|
||||
return nil
|
||||
}
|
||||
return v.ж.Tags()
|
||||
}
|
||||
|
||||
// IsTagged reports if a device is tagged
|
||||
// and therefore should not be treated as a
|
||||
// user owned device.
|
||||
// Currently, this function only handles tags set
|
||||
// via CLI ("forced tags" and preauthkeys)
|
||||
func (v NodeView) IsTagged() bool {
|
||||
if !v.Valid() {
|
||||
return false
|
||||
}
|
||||
return v.ж.IsTagged()
|
||||
}
|
||||
|
||||
// IsExpired returns whether the node registration has expired.
|
||||
func (v NodeView) IsExpired() bool {
|
||||
if !v.Valid() {
|
||||
return true
|
||||
}
|
||||
return v.ж.IsExpired()
|
||||
}
|
||||
|
||||
// IsEphemeral returns if the node is registered as an Ephemeral node.
|
||||
// https://tailscale.com/kb/1111/ephemeral-nodes/
|
||||
func (v NodeView) IsEphemeral() bool {
|
||||
if !v.Valid() {
|
||||
return false
|
||||
}
|
||||
return v.ж.IsEphemeral()
|
||||
}
|
||||
|
||||
// PeerChangeFromMapRequest takes a MapRequest and compares it to the node
|
||||
// to produce a PeerChange struct that can be used to updated the node and
|
||||
// inform peers about smaller changes to the node.
|
||||
func (v NodeView) PeerChangeFromMapRequest(req tailcfg.MapRequest) tailcfg.PeerChange {
|
||||
if !v.Valid() {
|
||||
return tailcfg.PeerChange{}
|
||||
}
|
||||
return v.ж.PeerChangeFromMapRequest(req)
|
||||
}
|
||||
|
||||
// GetFQDN returns the fully qualified domain name for the node.
|
||||
func (v NodeView) GetFQDN(baseDomain string) (string, error) {
|
||||
if !v.Valid() {
|
||||
return "", fmt.Errorf("failed to create valid FQDN: node view is invalid")
|
||||
}
|
||||
return v.ж.GetFQDN(baseDomain)
|
||||
}
|
||||
|
||||
// ExitRoutes returns a list of both exit routes if the
|
||||
// node has any exit routes enabled.
|
||||
// If none are enabled, it will return nil.
|
||||
func (v NodeView) ExitRoutes() []netip.Prefix {
|
||||
if !v.Valid() {
|
||||
return nil
|
||||
}
|
||||
return v.ж.ExitRoutes()
|
||||
}
|
||||
|
||||
// HasIP reports if a node has a given IP address.
|
||||
func (v NodeView) HasIP(i netip.Addr) bool {
|
||||
if !v.Valid() {
|
||||
return false
|
||||
}
|
||||
return v.ж.HasIP(i)
|
||||
}
|
||||
|
||||
// HasTag reports if a node has a given tag.
|
||||
func (v NodeView) HasTag(tag string) bool {
|
||||
if !v.Valid() {
|
||||
return false
|
||||
}
|
||||
return v.ж.HasTag(tag)
|
||||
}
|
||||
|
||||
// Prefixes returns the node IPs as netip.Prefix.
|
||||
func (v NodeView) Prefixes() []netip.Prefix {
|
||||
if !v.Valid() {
|
||||
return nil
|
||||
}
|
||||
return v.ж.Prefixes()
|
||||
}
|
||||
|
||||
// IPsAsString returns the node IPs as strings.
|
||||
func (v NodeView) IPsAsString() []string {
|
||||
if !v.Valid() {
|
||||
return nil
|
||||
}
|
||||
return v.ж.IPsAsString()
|
||||
}
|
||||
|
||||
|
135
hscontrol/types/types_clone.go
Normal file
135
hscontrol/types/types_clone.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/ptr"
|
||||
)
|
||||
|
||||
// Clone makes a deep copy of User.
|
||||
// The result aliases no memory with the original.
|
||||
func (src *User) Clone() *User {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := new(User)
|
||||
*dst = *src
|
||||
return dst
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _UserCloneNeedsRegeneration = User(struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
DisplayName string
|
||||
Email string
|
||||
ProviderIdentifier sql.NullString
|
||||
Provider string
|
||||
ProfilePicURL string
|
||||
}{})
|
||||
|
||||
// Clone makes a deep copy of Node.
|
||||
// The result aliases no memory with the original.
|
||||
func (src *Node) Clone() *Node {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := new(Node)
|
||||
*dst = *src
|
||||
dst.Endpoints = append(src.Endpoints[:0:0], src.Endpoints...)
|
||||
dst.Hostinfo = src.Hostinfo.Clone()
|
||||
if dst.IPv4 != nil {
|
||||
dst.IPv4 = ptr.To(*src.IPv4)
|
||||
}
|
||||
if dst.IPv6 != nil {
|
||||
dst.IPv6 = ptr.To(*src.IPv6)
|
||||
}
|
||||
dst.ForcedTags = append(src.ForcedTags[:0:0], src.ForcedTags...)
|
||||
if dst.AuthKeyID != nil {
|
||||
dst.AuthKeyID = ptr.To(*src.AuthKeyID)
|
||||
}
|
||||
dst.AuthKey = src.AuthKey.Clone()
|
||||
if dst.Expiry != nil {
|
||||
dst.Expiry = ptr.To(*src.Expiry)
|
||||
}
|
||||
if dst.LastSeen != nil {
|
||||
dst.LastSeen = ptr.To(*src.LastSeen)
|
||||
}
|
||||
dst.ApprovedRoutes = append(src.ApprovedRoutes[:0:0], src.ApprovedRoutes...)
|
||||
if dst.DeletedAt != nil {
|
||||
dst.DeletedAt = ptr.To(*src.DeletedAt)
|
||||
}
|
||||
if dst.IsOnline != nil {
|
||||
dst.IsOnline = ptr.To(*src.IsOnline)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _NodeCloneNeedsRegeneration = Node(struct {
|
||||
ID NodeID
|
||||
MachineKey key.MachinePublic
|
||||
NodeKey key.NodePublic
|
||||
DiscoKey key.DiscoPublic
|
||||
Endpoints []netip.AddrPort
|
||||
Hostinfo *tailcfg.Hostinfo
|
||||
IPv4 *netip.Addr
|
||||
IPv6 *netip.Addr
|
||||
Hostname string
|
||||
GivenName string
|
||||
UserID uint
|
||||
User User
|
||||
RegisterMethod string
|
||||
ForcedTags []string
|
||||
AuthKeyID *uint64
|
||||
AuthKey *PreAuthKey
|
||||
Expiry *time.Time
|
||||
LastSeen *time.Time
|
||||
ApprovedRoutes []netip.Prefix
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
IsOnline *bool
|
||||
}{})
|
||||
|
||||
// Clone makes a deep copy of PreAuthKey.
|
||||
// The result aliases no memory with the original.
|
||||
func (src *PreAuthKey) Clone() *PreAuthKey {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := new(PreAuthKey)
|
||||
*dst = *src
|
||||
dst.Tags = append(src.Tags[:0:0], src.Tags...)
|
||||
if dst.CreatedAt != nil {
|
||||
dst.CreatedAt = ptr.To(*src.CreatedAt)
|
||||
}
|
||||
if dst.Expiration != nil {
|
||||
dst.Expiration = ptr.To(*src.Expiration)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _PreAuthKeyCloneNeedsRegeneration = PreAuthKey(struct {
|
||||
ID uint64
|
||||
Key string
|
||||
UserID uint
|
||||
User User
|
||||
Reusable bool
|
||||
Ephemeral bool
|
||||
Used bool
|
||||
Tags []string
|
||||
CreatedAt *time.Time
|
||||
Expiration *time.Time
|
||||
}{})
|
270
hscontrol/types/types_view.go
Normal file
270
hscontrol/types/types_view.go
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by tailscale/cmd/viewer; DO NOT EDIT.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=User,Node,PreAuthKey
|
||||
|
||||
// View returns a read-only view of User.
|
||||
func (p *User) View() UserView {
|
||||
return UserView{ж: p}
|
||||
}
|
||||
|
||||
// UserView provides a read-only view over User.
|
||||
//
|
||||
// Its methods should only be called if `Valid()` returns true.
|
||||
type UserView struct {
|
||||
// ж is the underlying mutable value, named with a hard-to-type
|
||||
// character that looks pointy like a pointer.
|
||||
// It is named distinctively to make you think of how dangerous it is to escape
|
||||
// to callers. You must not let callers be able to mutate it.
|
||||
ж *User
|
||||
}
|
||||
|
||||
// Valid reports whether v's underlying value is non-nil.
|
||||
func (v UserView) Valid() bool { return v.ж != nil }
|
||||
|
||||
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||
// the original.
|
||||
func (v UserView) AsStruct() *User {
|
||||
if v.ж == nil {
|
||||
return nil
|
||||
}
|
||||
return v.ж.Clone()
|
||||
}
|
||||
|
||||
func (v UserView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||
|
||||
func (v *UserView) UnmarshalJSON(b []byte) error {
|
||||
if v.ж != nil {
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
var x User
|
||||
if err := json.Unmarshal(b, &x); err != nil {
|
||||
return err
|
||||
}
|
||||
v.ж = &x
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v UserView) Model() gorm.Model { return v.ж.Model }
|
||||
func (v UserView) Name() string { return v.ж.Name }
|
||||
func (v UserView) DisplayName() string { return v.ж.DisplayName }
|
||||
func (v UserView) Email() string { return v.ж.Email }
|
||||
func (v UserView) ProviderIdentifier() sql.NullString { return v.ж.ProviderIdentifier }
|
||||
func (v UserView) Provider() string { return v.ж.Provider }
|
||||
func (v UserView) ProfilePicURL() string { return v.ж.ProfilePicURL }
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _UserViewNeedsRegeneration = User(struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
DisplayName string
|
||||
Email string
|
||||
ProviderIdentifier sql.NullString
|
||||
Provider string
|
||||
ProfilePicURL string
|
||||
}{})
|
||||
|
||||
// View returns a read-only view of Node.
|
||||
func (p *Node) View() NodeView {
|
||||
return NodeView{ж: p}
|
||||
}
|
||||
|
||||
// NodeView provides a read-only view over Node.
|
||||
//
|
||||
// Its methods should only be called if `Valid()` returns true.
|
||||
type NodeView struct {
|
||||
// ж is the underlying mutable value, named with a hard-to-type
|
||||
// character that looks pointy like a pointer.
|
||||
// It is named distinctively to make you think of how dangerous it is to escape
|
||||
// to callers. You must not let callers be able to mutate it.
|
||||
ж *Node
|
||||
}
|
||||
|
||||
// Valid reports whether v's underlying value is non-nil.
|
||||
func (v NodeView) Valid() bool { return v.ж != nil }
|
||||
|
||||
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||
// the original.
|
||||
func (v NodeView) AsStruct() *Node {
|
||||
if v.ж == nil {
|
||||
return nil
|
||||
}
|
||||
return v.ж.Clone()
|
||||
}
|
||||
|
||||
func (v NodeView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||
|
||||
func (v *NodeView) UnmarshalJSON(b []byte) error {
|
||||
if v.ж != nil {
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
var x Node
|
||||
if err := json.Unmarshal(b, &x); err != nil {
|
||||
return err
|
||||
}
|
||||
v.ж = &x
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v NodeView) ID() NodeID { return v.ж.ID }
|
||||
func (v NodeView) MachineKey() key.MachinePublic { return v.ж.MachineKey }
|
||||
func (v NodeView) NodeKey() key.NodePublic { return v.ж.NodeKey }
|
||||
func (v NodeView) DiscoKey() key.DiscoPublic { return v.ж.DiscoKey }
|
||||
func (v NodeView) Endpoints() views.Slice[netip.AddrPort] { return views.SliceOf(v.ж.Endpoints) }
|
||||
func (v NodeView) Hostinfo() tailcfg.HostinfoView { return v.ж.Hostinfo.View() }
|
||||
func (v NodeView) IPv4() views.ValuePointer[netip.Addr] { return views.ValuePointerOf(v.ж.IPv4) }
|
||||
|
||||
func (v NodeView) IPv6() views.ValuePointer[netip.Addr] { return views.ValuePointerOf(v.ж.IPv6) }
|
||||
|
||||
func (v NodeView) Hostname() string { return v.ж.Hostname }
|
||||
func (v NodeView) GivenName() string { return v.ж.GivenName }
|
||||
func (v NodeView) UserID() uint { return v.ж.UserID }
|
||||
func (v NodeView) User() User { return v.ж.User }
|
||||
func (v NodeView) RegisterMethod() string { return v.ж.RegisterMethod }
|
||||
func (v NodeView) ForcedTags() views.Slice[string] { return views.SliceOf(v.ж.ForcedTags) }
|
||||
func (v NodeView) AuthKeyID() views.ValuePointer[uint64] { return views.ValuePointerOf(v.ж.AuthKeyID) }
|
||||
|
||||
func (v NodeView) AuthKey() PreAuthKeyView { return v.ж.AuthKey.View() }
|
||||
func (v NodeView) Expiry() views.ValuePointer[time.Time] { return views.ValuePointerOf(v.ж.Expiry) }
|
||||
|
||||
func (v NodeView) LastSeen() views.ValuePointer[time.Time] {
|
||||
return views.ValuePointerOf(v.ж.LastSeen)
|
||||
}
|
||||
|
||||
func (v NodeView) ApprovedRoutes() views.Slice[netip.Prefix] {
|
||||
return views.SliceOf(v.ж.ApprovedRoutes)
|
||||
}
|
||||
func (v NodeView) CreatedAt() time.Time { return v.ж.CreatedAt }
|
||||
func (v NodeView) UpdatedAt() time.Time { return v.ж.UpdatedAt }
|
||||
func (v NodeView) DeletedAt() views.ValuePointer[time.Time] {
|
||||
return views.ValuePointerOf(v.ж.DeletedAt)
|
||||
}
|
||||
|
||||
func (v NodeView) IsOnline() views.ValuePointer[bool] { return views.ValuePointerOf(v.ж.IsOnline) }
|
||||
|
||||
func (v NodeView) String() string { return v.ж.String() }
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _NodeViewNeedsRegeneration = Node(struct {
|
||||
ID NodeID
|
||||
MachineKey key.MachinePublic
|
||||
NodeKey key.NodePublic
|
||||
DiscoKey key.DiscoPublic
|
||||
Endpoints []netip.AddrPort
|
||||
Hostinfo *tailcfg.Hostinfo
|
||||
IPv4 *netip.Addr
|
||||
IPv6 *netip.Addr
|
||||
Hostname string
|
||||
GivenName string
|
||||
UserID uint
|
||||
User User
|
||||
RegisterMethod string
|
||||
ForcedTags []string
|
||||
AuthKeyID *uint64
|
||||
AuthKey *PreAuthKey
|
||||
Expiry *time.Time
|
||||
LastSeen *time.Time
|
||||
ApprovedRoutes []netip.Prefix
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
IsOnline *bool
|
||||
}{})
|
||||
|
||||
// View returns a read-only view of PreAuthKey.
|
||||
func (p *PreAuthKey) View() PreAuthKeyView {
|
||||
return PreAuthKeyView{ж: p}
|
||||
}
|
||||
|
||||
// PreAuthKeyView provides a read-only view over PreAuthKey.
|
||||
//
|
||||
// Its methods should only be called if `Valid()` returns true.
|
||||
type PreAuthKeyView struct {
|
||||
// ж is the underlying mutable value, named with a hard-to-type
|
||||
// character that looks pointy like a pointer.
|
||||
// It is named distinctively to make you think of how dangerous it is to escape
|
||||
// to callers. You must not let callers be able to mutate it.
|
||||
ж *PreAuthKey
|
||||
}
|
||||
|
||||
// Valid reports whether v's underlying value is non-nil.
|
||||
func (v PreAuthKeyView) Valid() bool { return v.ж != nil }
|
||||
|
||||
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||
// the original.
|
||||
func (v PreAuthKeyView) AsStruct() *PreAuthKey {
|
||||
if v.ж == nil {
|
||||
return nil
|
||||
}
|
||||
return v.ж.Clone()
|
||||
}
|
||||
|
||||
func (v PreAuthKeyView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||
|
||||
func (v *PreAuthKeyView) UnmarshalJSON(b []byte) error {
|
||||
if v.ж != nil {
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
var x PreAuthKey
|
||||
if err := json.Unmarshal(b, &x); err != nil {
|
||||
return err
|
||||
}
|
||||
v.ж = &x
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v PreAuthKeyView) ID() uint64 { return v.ж.ID }
|
||||
func (v PreAuthKeyView) Key() string { return v.ж.Key }
|
||||
func (v PreAuthKeyView) UserID() uint { return v.ж.UserID }
|
||||
func (v PreAuthKeyView) User() User { return v.ж.User }
|
||||
func (v PreAuthKeyView) Reusable() bool { return v.ж.Reusable }
|
||||
func (v PreAuthKeyView) Ephemeral() bool { return v.ж.Ephemeral }
|
||||
func (v PreAuthKeyView) Used() bool { return v.ж.Used }
|
||||
func (v PreAuthKeyView) Tags() views.Slice[string] { return views.SliceOf(v.ж.Tags) }
|
||||
func (v PreAuthKeyView) CreatedAt() views.ValuePointer[time.Time] {
|
||||
return views.ValuePointerOf(v.ж.CreatedAt)
|
||||
}
|
||||
|
||||
func (v PreAuthKeyView) Expiration() views.ValuePointer[time.Time] {
|
||||
return views.ValuePointerOf(v.ж.Expiration)
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _PreAuthKeyViewNeedsRegeneration = PreAuthKey(struct {
|
||||
ID uint64
|
||||
Key string
|
||||
UserID uint
|
||||
User User
|
||||
Reusable bool
|
||||
Ephemeral bool
|
||||
Used bool
|
||||
Tags []string
|
||||
CreatedAt *time.Time
|
||||
Expiration *time.Time
|
||||
}{})
|
Reference in New Issue
Block a user