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 }