src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

schema.go (8242B)


      1 package smithy
      2 
      3 import (
      4 	"fmt"
      5 	"strings"
      6 	"sync/atomic"
      7 	"unsafe"
      8 )
      9 
     10 // ShapeType is a type of Smithy shape.
     11 // See https://smithy.io/2.0/spec/idl.html#defining-shapes.
     12 type ShapeType int
     13 
     14 // Enumerates ShapeType per the Smithy IDL.
     15 const (
     16 	ShapeTypeBlob ShapeType = iota
     17 	ShapeTypeBoolean
     18 	ShapeTypeString
     19 	ShapeTypeTimestamp
     20 	ShapeTypeByte
     21 	ShapeTypeShort
     22 	ShapeTypeInteger
     23 	ShapeTypeLong
     24 	ShapeTypeFloat
     25 	ShapeTypeDocument
     26 	ShapeTypeDouble
     27 	ShapeTypeBigDecimal
     28 	ShapeTypeBigInteger
     29 	ShapeTypeEnum
     30 	ShapeTypeIntEnum
     31 	ShapeTypeList
     32 	ShapeTypeSet
     33 	ShapeTypeMap
     34 	ShapeTypeStructure
     35 	ShapeTypeUnion
     36 	ShapeTypeMember
     37 	ShapeTypeService
     38 	ShapeTypeResource
     39 	ShapeTypeOperation
     40 )
     41 
     42 // ShapeID fields of a Smithy shape ID.
     43 type ShapeID struct {
     44 	Namespace, Name, Member string
     45 }
     46 
     47 // String returns the IDL microformat for the shape ID.
     48 func (s ShapeID) String() string {
     49 	if s.Member == "" {
     50 		return fmt.Sprintf("%s#%s", s.Namespace, s.Name)
     51 	}
     52 	return fmt.Sprintf("%s#%s$%s", s.Namespace, s.Name, s.Member)
     53 }
     54 
     55 func stoid(s string) ShapeID {
     56 	ns, n, _ := strings.Cut(s, "#")
     57 	n, m, _ := strings.Cut(n, "$")
     58 	return ShapeID{ns, n, m}
     59 }
     60 
     61 // Schema encodes information about a shape from a Smithy model.
     62 //
     63 // Generated clients use schemas at runtime to dynamically (de)serialize
     64 // request/responses.
     65 type Schema struct {
     66 	id         ShapeID
     67 	typ        ShapeType
     68 	members    map[string]*Schema // member name -> schema
     69 	traits     map[ShapeID]Trait  // trait ID -> non-indexed traits only
     70 	indexed    []Trait            // indexed trait slots, sized to max index present
     71 	directMask uint64             // bitmask: bit i set means indexed[i] was declared directly on this schema
     72 	targetID   ShapeID            // for member schemas, the target's shape ID
     73 
     74 	// resolved on the fly and cached
     75 	listMember       atomic.Pointer[Schema]
     76 	mapKey, mapValue atomic.Pointer[Schema]
     77 
     78 	ext [numExtensionSlots]unsafe.Pointer // lazily-computed codec extensions, accessed atomically
     79 }
     80 
     81 // NewSchema creates a new Schema with the given shape ID and traits.
     82 func NewSchema(id ShapeID, typ ShapeType, numMembers int, ts ...Trait) *Schema {
     83 	s := &Schema{
     84 		id:      id,
     85 		typ:     typ,
     86 		members: make(map[string]*Schema, numMembers),
     87 	}
     88 	for _, t := range ts {
     89 		s.addTrait(t, true)
     90 	}
     91 	return s
     92 }
     93 
     94 func (s *Schema) addTrait(t Trait, direct bool) {
     95 	if it, ok := t.(IndexableTrait); ok {
     96 		idx := it.TraitIndex()
     97 		if idx >= len(s.indexed) {
     98 			s.indexed = append(s.indexed, make([]Trait, idx-len(s.indexed)+1)...)
     99 		}
    100 		s.indexed[idx] = t
    101 		if direct {
    102 			s.directMask |= 1 << uint(idx)
    103 		}
    104 		return
    105 	}
    106 
    107 	if s.traits == nil {
    108 		s.traits = map[ShapeID]Trait{}
    109 	}
    110 	s.traits[t.TraitID()] = t
    111 }
    112 
    113 // AddMember adds a member to the schema derived from the target, with
    114 // optional trait overrides. The member schema is returned for caller
    115 // reference.
    116 //
    117 // The member schema's effective trait view (accessed via [SchemaTrait])
    118 // inherits all of the target's traits, then applies the overrides. The
    119 // member's direct trait view (accessed via [SchemaDirectTrait]) contains
    120 // only the overrides, i.e. the traits declared directly on the member.
    121 func (s *Schema) AddMember(name string, target *Schema, ts ...Trait) *Schema {
    122 	m := &Schema{
    123 		id:         ShapeID{Member: name},
    124 		typ:        target.typ,
    125 		members:    target.members,
    126 		indexed:    cloneIndexed(target.indexed),
    127 		traits:     cloneTraits(target.traits),
    128 		directMask: 0, // inherited traits are not direct
    129 		targetID:   target.id,
    130 	}
    131 
    132 	// member-declared traits override and are direct
    133 	for _, t := range ts {
    134 		m.addTrait(t, true)
    135 	}
    136 
    137 	s.members[name] = m
    138 
    139 	// Invalidate cached extensions, schema structure changed.
    140 	for i := range s.ext {
    141 		atomic.StorePointer(&s.ext[i], nil)
    142 	}
    143 
    144 	return m
    145 }
    146 
    147 func cloneIndexed(src []Trait) []Trait {
    148 	if src == nil {
    149 		return nil
    150 	}
    151 	dst := make([]Trait, len(src))
    152 	copy(dst, src)
    153 	return dst
    154 }
    155 
    156 func cloneTraits(src map[ShapeID]Trait) map[ShapeID]Trait {
    157 	if src == nil {
    158 		return nil
    159 	}
    160 	dst := make(map[ShapeID]Trait, len(src))
    161 	for k, v := range src {
    162 		dst[k] = v
    163 	}
    164 	return dst
    165 }
    166 
    167 // ListMember returns the "member" schema for list types.
    168 func (s *Schema) ListMember() *Schema {
    169 	return s.lookup(&s.listMember, "member")
    170 }
    171 
    172 // MapKey returns the "key" schema for map types.
    173 func (s *Schema) MapKey() *Schema {
    174 	return s.lookup(&s.mapKey, "key")
    175 }
    176 
    177 // MapValue returns the "value" schema for map types.
    178 func (s *Schema) MapValue() *Schema {
    179 	return s.lookup(&s.mapValue, "value")
    180 }
    181 
    182 func (s *Schema) lookup(cached *atomic.Pointer[Schema], name string) *Schema {
    183 	if v := cached.Load(); v != nil {
    184 		return v
    185 	}
    186 
    187 	m, ok := s.members[name]
    188 	if !ok {
    189 		return nil
    190 	}
    191 
    192 	cached.Store(m)
    193 	return m
    194 }
    195 
    196 // MemberName returns the member component of the schema's shape ID.
    197 func (s *Schema) MemberName() string {
    198 	return s.id.Member
    199 }
    200 
    201 // ID returns the shape ID of the schema.
    202 func (s *Schema) ID() ShapeID {
    203 	return s.id
    204 }
    205 
    206 // TargetID returns the shape ID of the member's target shape.
    207 func (s *Schema) TargetID() ShapeID {
    208 	return s.targetID
    209 }
    210 
    211 // Type returns the shape type of the schema.
    212 func (s *Schema) Type() ShapeType {
    213 	return s.typ
    214 }
    215 
    216 // Member returns the member schema for the given name, or nil.
    217 func (s *Schema) Member(name string) *Schema {
    218 	return s.members[name]
    219 }
    220 
    221 // Members returns the schema's members as a map of name to schema.
    222 func (s *Schema) Members() map[string]*Schema {
    223 	return s.members
    224 }
    225 
    226 // OperationSchema describes an operation, which is essentially its own schema
    227 // with additional pointers to its input and output.
    228 type OperationSchema struct {
    229 	*Schema
    230 	Input, Output *Schema
    231 
    232 	inputStream, outputStream bool
    233 }
    234 
    235 // NewOperationSchema returns an OperationSchema for (input, output).
    236 func NewOperationSchema(op, input, output *Schema) *OperationSchema {
    237 	return &OperationSchema{
    238 		Schema:       op,
    239 		Input:        input,
    240 		Output:       output,
    241 		inputStream:  isEventStream(input),
    242 		outputStream: isEventStream(output),
    243 	}
    244 }
    245 
    246 // IsInputEventStream reports whether this is an input event stream.
    247 func (s *OperationSchema) IsInputEventStream() bool {
    248 	return s.inputStream
    249 }
    250 
    251 // IsOutputEventStream reports whether this is an output event stream.
    252 func (s *OperationSchema) IsOutputEventStream() bool {
    253 	return s.outputStream
    254 }
    255 
    256 // ServiceSchema describes a service shape.
    257 type ServiceSchema struct {
    258 	*Schema
    259 	Version string
    260 }
    261 
    262 // NewServiceSchema returns a ServiceSchema for the given service shape.
    263 func NewServiceSchema(schema *Schema, version string) *ServiceSchema {
    264 	return &ServiceSchema{Schema: schema, Version: version}
    265 }
    266 
    267 // SchemaTrait returns the target trait on the schema if it exists.
    268 //
    269 // For member schemas this returns the effective trait, which is the trait
    270 // declared directly on the member if present, else the trait inherited from
    271 // the target shape.
    272 func SchemaTrait[T Trait](s *Schema) (T, bool) {
    273 	return schemaTrait[T](s, false)
    274 }
    275 
    276 // SchemaDirectTrait returns the target trait on the schema if it was
    277 // declared directly on the schema.
    278 //
    279 // For member schemas this returns the trait only if it was declared on the
    280 // member itself, ignoring any trait inherited from the target shape. For
    281 // non-member schemas this is equivalent to [SchemaTrait].
    282 func SchemaDirectTrait[T Trait](s *Schema) (T, bool) {
    283 	return schemaTrait[T](s, true)
    284 }
    285 
    286 func schemaTrait[T Trait](s *Schema, directOnly bool) (T, bool) {
    287 	var zero T
    288 
    289 	if s == nil {
    290 		return zero, false
    291 	}
    292 
    293 	if it, ok := Trait(zero).(IndexableTrait); ok {
    294 		idx := it.TraitIndex()
    295 		if idx >= len(s.indexed) {
    296 			return zero, false
    297 		}
    298 		if directOnly && s.directMask&(1<<uint(idx)) == 0 {
    299 			return zero, false
    300 		}
    301 		tt, ok := s.indexed[idx].(T)
    302 		return tt, ok
    303 	}
    304 
    305 	opaque, ok := s.traits[zero.TraitID()]
    306 	if !ok {
    307 		return zero, false
    308 	}
    309 
    310 	tt, ok := opaque.(T)
    311 	return tt, ok
    312 }
    313 
    314 // indexStreaming is the indexed trait slot for @streaming, mirrored from
    315 // traits.indexStreaming. We can't import the traits package from here due to a
    316 // circular dependency.
    317 const indexStreaming = 17
    318 
    319 func isEventStream(s *Schema) bool {
    320 	if s == nil {
    321 		return false
    322 	}
    323 	for _, m := range s.members {
    324 		if m.typ != ShapeTypeUnion {
    325 			continue
    326 		}
    327 		if len(m.indexed) > indexStreaming && m.indexed[indexStreaming] != nil {
    328 			return true
    329 		}
    330 	}
    331 	return false
    332 }