smithy-schema 1.0.0.pre0 → 1.0.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/VERSION +1 -1
- data/lib/smithy-schema/document.rb +172 -0
- data/lib/smithy-schema/document_utils/deserializer.rb +125 -0
- data/lib/smithy-schema/document_utils/serializer.rb +181 -0
- data/lib/smithy-schema/empty_structure.rb +10 -0
- data/lib/smithy-schema/shapes.rb +147 -100
- data/lib/smithy-schema/structure.rb +1 -8
- data/lib/smithy-schema/type_registry.rb +125 -0
- data/lib/smithy-schema/union.rb +5 -5
- data/lib/smithy-schema.rb +7 -3
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7969227c178251f03785e6acea6cec078f3a5b58f266442dc3d980e6d4b91df5
|
4
|
+
data.tar.gz: 9308246739c8b7f192dce70a2dc7dcd7a26aa8446f8e0be1da54c724de30f4ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8098ac1b23fade8569d4e5bf57f645ec5cf7d2783740327dcad4564b19b6f86e442a5d7b8af092170e581ca2948f6ba4ca47d2e9874b58ba333d08a9826a52cb
|
7
|
+
data.tar.gz: '03649f8030eea28a80d0a7f37d306f6ca33933437e56c7acb4a579f9ea4423d3b0c01d819c685096392792031e1a6630e09b0085351baf18dea748203dda37ad'
|
data/CHANGELOG.md
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.0.
|
1
|
+
1.0.0.pre1
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'document_utils/deserializer'
|
4
|
+
require_relative 'document_utils/serializer'
|
5
|
+
require 'delegate'
|
6
|
+
|
7
|
+
module Smithy
|
8
|
+
module Schema
|
9
|
+
# A Smithy document, representing typed or untyped data from the Smithy data model.
|
10
|
+
# The Document class delegates to the underlying data object while providing additional
|
11
|
+
# document-specific functionality. The document will represent protocol-agnostic
|
12
|
+
# data structures in the Smithy data model.
|
13
|
+
#
|
14
|
+
# This class includes capabilities for:
|
15
|
+
#
|
16
|
+
# - Serialization and deserialization of document data
|
17
|
+
# - Type-aware data handling
|
18
|
+
# - Support for JSON document format
|
19
|
+
#
|
20
|
+
# To create a Document using various input formats, use {Document.create}
|
21
|
+
# @example Basic usage with a document
|
22
|
+
# document = Document.new(name: "document")
|
23
|
+
# document # => { "name" => "document" }
|
24
|
+
class Document < ::SimpleDelegator
|
25
|
+
# A Smithy document, representing typed or untyped data from the Smithy data model.
|
26
|
+
# This class delegates to the underlying data object while providing additional
|
27
|
+
# document-specific functionality.
|
28
|
+
# @param [Object] data document data
|
29
|
+
# @param [Hash] options
|
30
|
+
# @option options [String] :discriminator This value is used to identify a specific
|
31
|
+
# shape. This is equivalent of a Smithy shape ID.
|
32
|
+
def initialize(data, options = {})
|
33
|
+
@data = data
|
34
|
+
@discriminator = options[:discriminator]
|
35
|
+
super(@data)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the discriminator value for the document.
|
39
|
+
#
|
40
|
+
# @return [String, nil] discriminator
|
41
|
+
attr_reader :discriminator
|
42
|
+
|
43
|
+
# Serializes a {Document} with optional formatting.
|
44
|
+
#
|
45
|
+
# @param [TypeRegistry] type_registry Registry is required for identifying
|
46
|
+
# and validating typed documents
|
47
|
+
# @param [Hash] opts Formatting options
|
48
|
+
# @option opts [Boolean] :timestamp_format Whether to use the `timestampFormat`
|
49
|
+
# trait or ignore it. The `timestampFormat` trait is ignored by default.
|
50
|
+
# @option opts [Boolean] :json_name Whether to use `jsonName` trait
|
51
|
+
# or just member name. The `jsonName` trait is ignored by default.
|
52
|
+
def serialize(type_registry, opts = {})
|
53
|
+
validate_document(type_registry)
|
54
|
+
|
55
|
+
opts[:type_registry] = type_registry
|
56
|
+
opts[:json] = true
|
57
|
+
serializer = DocumentUtils::Serializer.new(opts)
|
58
|
+
serializer.format_document_data(type_registry[@discriminator], @data)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Deserializes a {Document} into a type.
|
62
|
+
#
|
63
|
+
# @param [TypeRegistry, nil] type_registry Registry is required for
|
64
|
+
# identifying and deserializing typed documents. Either this or shape
|
65
|
+
# must be provided.
|
66
|
+
# @param [StructureShape, nil] shape shape to use for deserialization.
|
67
|
+
# If provided, this shape takes precedence over the document's discriminator.
|
68
|
+
# The shape must have a type.
|
69
|
+
def deserialize(type_registry: nil, shape: nil)
|
70
|
+
msg = 'either a type registry or a structure shape must be provided to deserialize'
|
71
|
+
raise ArgumentError, msg unless type_registry || shape
|
72
|
+
|
73
|
+
type_registry.nil? ? validate_shape(shape) : validate_document(type_registry)
|
74
|
+
|
75
|
+
shape ||= type_registry[@discriminator]
|
76
|
+
deserializer = DocumentUtils::Deserializer.new(type_registry: type_registry)
|
77
|
+
deserializer.deserialize(@data, shape, shape.type.new)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def validate_document(type_registry)
|
83
|
+
msg = 'unable validate typed document - must have a discriminator'
|
84
|
+
raise ArgumentError, msg unless @discriminator
|
85
|
+
|
86
|
+
msg = 'document discriminator not found in type registry'
|
87
|
+
raise ArgumentError, msg unless type_registry.key?(@discriminator)
|
88
|
+
end
|
89
|
+
|
90
|
+
def validate_shape(shape)
|
91
|
+
msg = 'invalid shape - must be a structure shape with type'
|
92
|
+
raise ArgumentError, msg unless shape.is_a?(Shapes::StructureShape) && shape.type
|
93
|
+
end
|
94
|
+
|
95
|
+
class << self
|
96
|
+
# Create a {Document} from various input formats.
|
97
|
+
#
|
98
|
+
# @param [Object] data Input data could be one of the following: a Ruby object,
|
99
|
+
# a Struct type, or a parsed JSON with type discriminator key.
|
100
|
+
# @param [TypeRegistry, nil] type_registry Type Registry is required for
|
101
|
+
# identifying and serializing typed documents. Option for untyped documents.
|
102
|
+
# @return [Document] document
|
103
|
+
#
|
104
|
+
# @example Ruby Object as input
|
105
|
+
# # creating an untyped document
|
106
|
+
# document = Smithy::Schema::Document.create(foo: "bar")
|
107
|
+
# # => {"foo" => "bar"}
|
108
|
+
# @example Structure type as input
|
109
|
+
# structure = some_structure.type.new(some_data)
|
110
|
+
# # => #<struct SampleService::Types::Structure ...>
|
111
|
+
#
|
112
|
+
# # Type Registry is required to properly serialize
|
113
|
+
# document = Smithy::Schema::Document.create(structure, type_registry)
|
114
|
+
# # => #<Smithy::Schema::Document ...>
|
115
|
+
# @example JSON data
|
116
|
+
# # given the following json data
|
117
|
+
# parsed_json = {
|
118
|
+
# "__type" => "smithy.ruby.tests#Structure",
|
119
|
+
# "string" => "hello"
|
120
|
+
# }
|
121
|
+
#
|
122
|
+
# document = serializer.create(parsed_json, type_registry)
|
123
|
+
# # => an instance of Smithy::Schema::Document
|
124
|
+
# document.discriminator
|
125
|
+
# # => "smithy.ruby.tests#Structure"
|
126
|
+
def create(data, type_registry = nil)
|
127
|
+
raise ArgumentError, 'invalid data - document cannot be nil' if data.nil?
|
128
|
+
|
129
|
+
return untyped_document(data) if type_registry.nil?
|
130
|
+
|
131
|
+
validate_typed_data(data, type_registry)
|
132
|
+
typed_document(data, type_registry)
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def discriminator?(data)
|
138
|
+
data.is_a?(Hash) && data.key?('__type')
|
139
|
+
end
|
140
|
+
|
141
|
+
def untyped_document(data)
|
142
|
+
serializer = DocumentUtils::Serializer.new
|
143
|
+
new(serializer.serialize_untyped(data))
|
144
|
+
end
|
145
|
+
|
146
|
+
def typed_document(data, type_registry)
|
147
|
+
opts = { type_registry: type_registry }
|
148
|
+
case data
|
149
|
+
when Structure
|
150
|
+
shape = type_registry.shape_by_type(data.class)
|
151
|
+
else
|
152
|
+
opts = opts.merge(json: true, json_name: true)
|
153
|
+
shape = type_registry[data['__type']]
|
154
|
+
end
|
155
|
+
serializer = DocumentUtils::Serializer.new(opts)
|
156
|
+
new(serializer.format_document_data(shape, data), discriminator: shape.id)
|
157
|
+
end
|
158
|
+
|
159
|
+
def validate_typed_data(data, type_registry)
|
160
|
+
case data
|
161
|
+
when Structure
|
162
|
+
msg = 'given type class not found in type registry'
|
163
|
+
raise ArgumentError, msg unless type_registry.shape_by_type?(data.class)
|
164
|
+
else
|
165
|
+
msg = 'document discriminator not found in type registry'
|
166
|
+
raise ArgumentError, msg if discriminator?(data) && !type_registry.key?(data['__type'])
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Schema
|
5
|
+
module DocumentUtils
|
6
|
+
# Deserializes document data into a type.
|
7
|
+
# @api private
|
8
|
+
class Deserializer
|
9
|
+
include Shapes
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@type_registry = options[:type_registry]
|
13
|
+
end
|
14
|
+
|
15
|
+
def deserialize(data, shape, target)
|
16
|
+
ref = shape.is_a?(ShapeRef) ? shape : ShapeRef.new(shape: shape)
|
17
|
+
shape(ref, data, target)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def shape(ref, value, target = nil) # rubocop:disable Metrics/CyclomaticComplexity
|
23
|
+
case ref.shape
|
24
|
+
when BlobShape then Base64.strict_decode64(value)
|
25
|
+
when DocumentShape then document(value)
|
26
|
+
when FloatShape then float(value)
|
27
|
+
when ListShape then list(ref, value, target)
|
28
|
+
when MapShape then map(ref, value, target)
|
29
|
+
when StructureShape then structure(ref, value, target)
|
30
|
+
when TimestampShape then timestamp(value)
|
31
|
+
when UnionShape then union(ref, value, target)
|
32
|
+
else value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def document(values)
|
37
|
+
return values unless values.is_a?(Hash) && values.key?('__type')
|
38
|
+
|
39
|
+
msg = 'invalid document - document discriminator not found in type registry'
|
40
|
+
raise ArgumentError, msg unless @type_registry.key?(values['__type'])
|
41
|
+
|
42
|
+
shape(ShapeRef.new(shape: @type_registry[values['__type']]), values)
|
43
|
+
end
|
44
|
+
|
45
|
+
def float(value)
|
46
|
+
case value
|
47
|
+
when 'Infinity' then ::Float::INFINITY
|
48
|
+
when '-Infinity' then -::Float::INFINITY
|
49
|
+
when 'NaN' then ::Float::NAN
|
50
|
+
when nil then nil
|
51
|
+
else value.to_f
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def list(ref, values, target = nil)
|
56
|
+
return if values.nil?
|
57
|
+
|
58
|
+
target = [] if target.nil?
|
59
|
+
values.each do |value|
|
60
|
+
target << shape(ref.shape.member, value) unless value.nil?
|
61
|
+
end
|
62
|
+
target
|
63
|
+
end
|
64
|
+
|
65
|
+
def map(ref, values, target = nil)
|
66
|
+
return if values.nil?
|
67
|
+
|
68
|
+
target = {} if target.nil?
|
69
|
+
values.each do |key, value|
|
70
|
+
target[key] = shape(ref.shape.value, value) unless value.nil?
|
71
|
+
end
|
72
|
+
target
|
73
|
+
end
|
74
|
+
|
75
|
+
def structure(ref, values, target = nil)
|
76
|
+
return if values.nil?
|
77
|
+
|
78
|
+
target = ref.shape.type.new if target.nil?
|
79
|
+
ref.shape.members.each do |member_name, member_ref|
|
80
|
+
value = values[location_name(member_ref)]
|
81
|
+
target[member_name] = shape(member_ref, value) unless value.nil?
|
82
|
+
end
|
83
|
+
target
|
84
|
+
end
|
85
|
+
|
86
|
+
def timestamp(value)
|
87
|
+
case value
|
88
|
+
when nil then nil
|
89
|
+
when Numeric
|
90
|
+
Time.at(value).utc
|
91
|
+
when /^[\d.]+$/
|
92
|
+
Time.at(value.to_f).utc
|
93
|
+
else
|
94
|
+
begin
|
95
|
+
fractional_time = Time.parse(value).to_f
|
96
|
+
Time.at(fractional_time).utc
|
97
|
+
rescue ArgumentError
|
98
|
+
raise "unhandled timestamp format `#{value}'"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def union(ref, values, target = nil) # rubocop:disable Metrics/AbcSize
|
104
|
+
ref.shape.members.each do |member_name, member_ref|
|
105
|
+
value = values[location_name(member_ref)]
|
106
|
+
next if value.nil?
|
107
|
+
|
108
|
+
target = ref.shape.member_type(member_name) if target.nil?
|
109
|
+
return target.new(member_name => shape(member_ref, value))
|
110
|
+
end
|
111
|
+
|
112
|
+
values.delete('__type')
|
113
|
+
key, value = values.first
|
114
|
+
ref.shape.member_type(:unknown).new(key, value)
|
115
|
+
end
|
116
|
+
|
117
|
+
def location_name(ref)
|
118
|
+
return ref.member_name unless @json_name
|
119
|
+
|
120
|
+
ref.traits['smithy.api#jsonName'] || ref.member_name
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
module Smithy
|
7
|
+
module Schema
|
8
|
+
module DocumentUtils
|
9
|
+
# Serializes data into a document data.
|
10
|
+
# @api private
|
11
|
+
class Serializer
|
12
|
+
include Shapes
|
13
|
+
|
14
|
+
def initialize(options = {})
|
15
|
+
@type_registry = options[:type_registry]
|
16
|
+
@json = options[:json] || false
|
17
|
+
@json_name = options[:json_name] || false
|
18
|
+
@timestamp_format = options[:timestamp_format] || false
|
19
|
+
end
|
20
|
+
|
21
|
+
def format_document_data(shape, data)
|
22
|
+
ref = shape.is_a?(ShapeRef) ? shape : ShapeRef.new(shape: shape)
|
23
|
+
document_data = shape(ref, data)
|
24
|
+
document_data['__type'] = shape.id
|
25
|
+
document_data
|
26
|
+
end
|
27
|
+
|
28
|
+
def serialize_untyped(values)
|
29
|
+
return if values.nil?
|
30
|
+
|
31
|
+
case values
|
32
|
+
when Time then values.utc.to_i # timestamp format is "epoch-seconds" by default
|
33
|
+
when Hash
|
34
|
+
values.each_with_object({}) do |(k, v), h|
|
35
|
+
h[k.to_s] = serialize_untyped(v)
|
36
|
+
end
|
37
|
+
when Array then values.map { |d| serialize_untyped(d) }
|
38
|
+
else values
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def shape(ref, values) # rubocop:disable Metrics/CyclomaticComplexity
|
45
|
+
case ref.shape
|
46
|
+
when BlobShape then blob(values)
|
47
|
+
when DocumentShape then document(values)
|
48
|
+
when FloatShape then float(values)
|
49
|
+
when ListShape then list(ref, values)
|
50
|
+
when MapShape then map(ref, values)
|
51
|
+
when StructureShape then structure(ref, values)
|
52
|
+
when TimestampShape then timestamp(ref, values)
|
53
|
+
when UnionShape then union(ref, values)
|
54
|
+
else values
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def blob(value)
|
59
|
+
return value if @json # blob is already encoded
|
60
|
+
|
61
|
+
Base64.strict_encode64(value.respond_to?(:read) ? value.read : value)
|
62
|
+
end
|
63
|
+
|
64
|
+
def document(values)
|
65
|
+
shape = document_shape(values)
|
66
|
+
return values unless shape
|
67
|
+
|
68
|
+
format_document_data(shape, values)
|
69
|
+
end
|
70
|
+
|
71
|
+
def document_shape(values)
|
72
|
+
case values
|
73
|
+
when Structure
|
74
|
+
@type_registry.shape_by_type(values.class)
|
75
|
+
when Hash
|
76
|
+
@type_registry[values['__type']]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def float(value)
|
81
|
+
if value == ::Float::INFINITY
|
82
|
+
'Infinity'
|
83
|
+
elsif value == -::Float::INFINITY
|
84
|
+
'-Infinity'
|
85
|
+
elsif value.to_f.nan?
|
86
|
+
'NaN'
|
87
|
+
else
|
88
|
+
value.to_f
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def list(ref, values)
|
93
|
+
return if values.nil?
|
94
|
+
|
95
|
+
shape = ref.shape
|
96
|
+
values.collect do |value|
|
97
|
+
shape(shape.member, value)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def map(ref, values)
|
102
|
+
return if values.nil?
|
103
|
+
|
104
|
+
shape = ref.shape
|
105
|
+
values.each.with_object({}) do |(key, value), data|
|
106
|
+
data[key.to_s] = shape(shape.value, value)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def structure(ref, values)
|
111
|
+
return if values.nil?
|
112
|
+
|
113
|
+
ref.shape.members.each_with_object({}) do |(member_name, member_ref), data|
|
114
|
+
value = resolve_value(member_name, member_ref, values.to_h)
|
115
|
+
data[location_name(member_ref)] = shape(member_ref, value) unless value.nil?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def timestamp(ref, value)
|
120
|
+
value = normalize_timestamp_value(value)
|
121
|
+
return value.to_i unless @timestamp_format
|
122
|
+
|
123
|
+
trait = 'smithy.api#timestampFormat'
|
124
|
+
case ref.traits[trait] || ref.shape.traits[trait]
|
125
|
+
when 'date-time' then value.utc.iso8601
|
126
|
+
when 'http-date' then value.utc.httpdate
|
127
|
+
else
|
128
|
+
# default to epoch-seconds
|
129
|
+
value.to_i
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def union(ref, values)
|
134
|
+
return if values.nil?
|
135
|
+
|
136
|
+
data = {}
|
137
|
+
if values.is_a?(Union)
|
138
|
+
_name, member_ref = ref.shape.member_by_type(values.class)
|
139
|
+
data[location_name(member_ref)] = shape(member_ref, values)
|
140
|
+
else
|
141
|
+
key, value = values.first
|
142
|
+
if (member_ref = resolve_member_ref(ref, key))
|
143
|
+
data[location_name(member_ref)] = shape(member_ref, value)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
data
|
147
|
+
end
|
148
|
+
|
149
|
+
def location_name(ref)
|
150
|
+
return ref.member_name unless @json_name
|
151
|
+
|
152
|
+
ref.traits['smithy.api#jsonName'] || ref.member_name
|
153
|
+
end
|
154
|
+
|
155
|
+
def normalize_timestamp_value(value)
|
156
|
+
case value
|
157
|
+
when Time then value
|
158
|
+
when Numeric then Time.at(value)
|
159
|
+
else Time.parse(value)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def resolve_member_ref(ref, name)
|
164
|
+
return ref.shape.member(name) if ref.shape.member?(name)
|
165
|
+
|
166
|
+
ref.shape.members.values.find do |member_ref|
|
167
|
+
member_ref.traits['smithy.api#jsonName'] == name || member_ref.member_name == name
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def resolve_value(member_name, member_ref, values)
|
172
|
+
if (json_name = member_ref.traits['smithy.api#jsonName'])
|
173
|
+
value = values[json_name]
|
174
|
+
return value unless value.nil?
|
175
|
+
end
|
176
|
+
values[member_name] || values[member_ref.member_name]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Schema
|
5
|
+
# An empty Struct that includes the {Schema::Structure} module.
|
6
|
+
class EmptyStructure < Struct.new(nil) # rubocop:disable Style/StructInheritance
|
7
|
+
include Smithy::Schema::Structure
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/lib/smithy-schema/shapes.rb
CHANGED
@@ -9,6 +9,7 @@ module Smithy
|
|
9
9
|
def initialize(options = {})
|
10
10
|
@id = options[:id]
|
11
11
|
@traits = options[:traits] || {}
|
12
|
+
@metadata = {}
|
12
13
|
end
|
13
14
|
|
14
15
|
# @return [String, nil] Absolute shape ID from model
|
@@ -16,53 +17,46 @@ module Smithy
|
|
16
17
|
|
17
18
|
# @return [Hash<String, Object>]
|
18
19
|
attr_accessor :traits
|
19
|
-
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
super
|
25
|
-
@members = {}
|
26
|
-
@names_by_member_name = {}
|
21
|
+
# @return [Object]
|
22
|
+
def [](key)
|
23
|
+
@metadata[key]
|
27
24
|
end
|
28
25
|
|
29
|
-
# @
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
attr_accessor :names_by_member_name
|
34
|
-
|
35
|
-
# @return [Class, nil]
|
36
|
-
attr_accessor :type
|
37
|
-
|
38
|
-
# @return [MemberShape]
|
39
|
-
def add_member(name, member_name, shape, traits: {})
|
40
|
-
@names_by_member_name[member_name] = name
|
41
|
-
@members[name] = MemberShape.new(member_name, shape, traits: traits)
|
26
|
+
# @param [Symbol] key
|
27
|
+
# @param [Object] value
|
28
|
+
def []=(key, value)
|
29
|
+
@metadata[key] = value
|
42
30
|
end
|
31
|
+
end
|
43
32
|
|
44
|
-
|
45
|
-
|
46
|
-
def
|
47
|
-
@
|
33
|
+
# A reference to a shape.
|
34
|
+
class ShapeRef
|
35
|
+
def initialize(options = {})
|
36
|
+
@shape = options[:shape]
|
37
|
+
@member_name = options[:member_name]
|
38
|
+
@traits = options[:traits] || {}
|
39
|
+
@metadata = {}
|
48
40
|
end
|
49
41
|
|
50
|
-
# @
|
51
|
-
|
52
|
-
def member(name)
|
53
|
-
@members[name]
|
54
|
-
end
|
42
|
+
# @return [Shape]
|
43
|
+
attr_reader :shape
|
55
44
|
|
56
|
-
# @
|
57
|
-
|
58
|
-
|
59
|
-
|
45
|
+
# @return [String, nil]
|
46
|
+
attr_reader :member_name
|
47
|
+
|
48
|
+
# @return [Hash<String, Object>]
|
49
|
+
attr_reader :traits
|
50
|
+
|
51
|
+
# @return [Object]
|
52
|
+
def [](key)
|
53
|
+
@metadata[key]
|
60
54
|
end
|
61
55
|
|
62
|
-
# @param [
|
63
|
-
# @
|
64
|
-
def
|
65
|
-
@
|
56
|
+
# @param [Symbol] key
|
57
|
+
# @param [Object] value
|
58
|
+
def []=(key, value)
|
59
|
+
@metadata[key] = value
|
66
60
|
end
|
67
61
|
end
|
68
62
|
|
@@ -78,7 +72,7 @@ module Smithy
|
|
78
72
|
yield self if block_given?
|
79
73
|
end
|
80
74
|
|
81
|
-
# @return [String
|
75
|
+
# @return [String]
|
82
76
|
attr_accessor :name
|
83
77
|
|
84
78
|
# @return [String, nil]
|
@@ -122,16 +116,16 @@ module Smithy
|
|
122
116
|
yield self if block_given?
|
123
117
|
end
|
124
118
|
|
125
|
-
# @return [String
|
119
|
+
# @return [String]
|
126
120
|
attr_accessor :name
|
127
121
|
|
128
|
-
# @return [
|
122
|
+
# @return [ShapeRef]
|
129
123
|
attr_accessor :input
|
130
124
|
|
131
|
-
# @return [
|
125
|
+
# @return [ShapeRef]
|
132
126
|
attr_accessor :output
|
133
127
|
|
134
|
-
# @return [Array<
|
128
|
+
# @return [Array<ShapeRef>]
|
135
129
|
attr_accessor :errors
|
136
130
|
end
|
137
131
|
|
@@ -148,87 +142,157 @@ module Smithy
|
|
148
142
|
class DocumentShape < Shape; end
|
149
143
|
|
150
144
|
# Represents an Enum shape.
|
151
|
-
class EnumShape <
|
145
|
+
class EnumShape < Shape
|
146
|
+
def initialize(options = {})
|
147
|
+
super
|
148
|
+
@members = {}
|
149
|
+
end
|
150
|
+
|
151
|
+
# @return [Hash<Symbol, ShapeRef>]
|
152
|
+
attr_accessor :members
|
153
|
+
|
154
|
+
# @return [ShapeRef]
|
155
|
+
def add_member(name, shape_ref)
|
156
|
+
@members[name] = shape_ref
|
157
|
+
end
|
158
|
+
|
159
|
+
# @param [Symbol] name
|
160
|
+
# @return [Boolean]
|
161
|
+
def member?(name)
|
162
|
+
@members.key?(name)
|
163
|
+
end
|
164
|
+
|
165
|
+
# @param [Symbol] name
|
166
|
+
# @return [ShapeRef, nil]
|
167
|
+
def member(name)
|
168
|
+
@members[name]
|
169
|
+
end
|
170
|
+
end
|
152
171
|
|
153
172
|
# Represents the following shapes: Byte, Short, Integer, Long, BigInteger.
|
154
173
|
class IntegerShape < Shape; end
|
155
174
|
|
156
175
|
# Represents an IntEnum shape.
|
157
|
-
class IntEnumShape <
|
176
|
+
class IntEnumShape < Shape
|
177
|
+
def initialize(options = {})
|
178
|
+
super
|
179
|
+
@members = {}
|
180
|
+
end
|
181
|
+
|
182
|
+
# @return [Hash<Symbol, ShapeRef>]
|
183
|
+
attr_accessor :members
|
184
|
+
|
185
|
+
# @return [ShapeRef]
|
186
|
+
def add_member(name, shape_ref)
|
187
|
+
@members[name] = shape_ref
|
188
|
+
end
|
189
|
+
|
190
|
+
# @param [Symbol] name
|
191
|
+
# @return [Boolean]
|
192
|
+
def member?(name)
|
193
|
+
@members.key?(name)
|
194
|
+
end
|
195
|
+
|
196
|
+
# @param [Symbol] name
|
197
|
+
# @return [ShapeRef, nil]
|
198
|
+
def member(name)
|
199
|
+
@members[name]
|
200
|
+
end
|
201
|
+
end
|
158
202
|
|
159
203
|
# Represents both Float and Double shapes.
|
160
204
|
class FloatShape < Shape; end
|
161
205
|
|
162
206
|
# Represents a List shape.
|
163
207
|
class ListShape < Shape
|
164
|
-
|
165
|
-
super
|
166
|
-
@member = nil
|
167
|
-
end
|
168
|
-
|
169
|
-
# @return [MemberShape, nil]
|
208
|
+
# @return [ShapeRef]
|
170
209
|
attr_accessor :member
|
171
|
-
|
172
|
-
def set_member(shape, traits: {})
|
173
|
-
@member = MemberShape.new('member', shape, traits: traits)
|
174
|
-
end
|
175
210
|
end
|
176
211
|
|
177
212
|
# Represents a Map shape.
|
178
213
|
class MapShape < Shape
|
214
|
+
# @return [ShapeRef]
|
215
|
+
attr_accessor :key
|
216
|
+
|
217
|
+
# @return [ShapeRef]
|
218
|
+
attr_accessor :value
|
219
|
+
end
|
220
|
+
|
221
|
+
# Represents a String shape.
|
222
|
+
class StringShape < Shape; end
|
223
|
+
|
224
|
+
# Represents a Structure shape.
|
225
|
+
class StructureShape < Shape
|
179
226
|
def initialize(options = {})
|
180
227
|
super
|
181
|
-
@
|
182
|
-
@value = nil
|
228
|
+
@members = {}
|
183
229
|
end
|
184
230
|
|
185
|
-
# @return [
|
186
|
-
attr_accessor :
|
231
|
+
# @return [Hash<Symbol, ShapeRef>]
|
232
|
+
attr_accessor :members
|
187
233
|
|
188
|
-
# @return [
|
189
|
-
attr_accessor :
|
234
|
+
# @return [Class]
|
235
|
+
attr_accessor :type
|
190
236
|
|
191
|
-
|
192
|
-
|
237
|
+
# @return [ShapeRef]
|
238
|
+
def add_member(name, shape_ref)
|
239
|
+
@members[name] = shape_ref
|
193
240
|
end
|
194
241
|
|
195
|
-
|
196
|
-
|
242
|
+
# @param [Symbol] name
|
243
|
+
# @return [Boolean]
|
244
|
+
def member?(name)
|
245
|
+
@members.key?(name)
|
197
246
|
end
|
198
|
-
end
|
199
|
-
|
200
|
-
# Represents a String shape.
|
201
|
-
class StringShape < Shape; end
|
202
247
|
|
203
|
-
|
204
|
-
|
248
|
+
# @param [Symbol] name
|
249
|
+
# @return [ShapeRef, nil]
|
250
|
+
def member(name)
|
251
|
+
@members[name]
|
252
|
+
end
|
205
253
|
end
|
206
254
|
|
207
255
|
# Represents a Timestamp shape.
|
208
256
|
class TimestampShape < Shape; end
|
209
257
|
|
210
258
|
# Represents both Union and EventStream shapes.
|
211
|
-
class UnionShape <
|
259
|
+
class UnionShape < Shape
|
212
260
|
def initialize(options = {})
|
213
261
|
super
|
262
|
+
@members = {}
|
214
263
|
@member_types = {}
|
215
264
|
@members_by_type = {}
|
216
265
|
end
|
217
266
|
|
267
|
+
# @return [Hash<Symbol, ShapeRef>]
|
268
|
+
attr_accessor :members
|
269
|
+
|
218
270
|
# @return [Hash<Symbol, Class>]
|
219
|
-
|
271
|
+
attr_reader :member_types
|
272
|
+
|
273
|
+
# @return [Hash<Class, [String, ShapeRef]>]
|
274
|
+
attr_reader :members_by_type
|
220
275
|
|
221
|
-
# @return [
|
222
|
-
attr_accessor :
|
276
|
+
# @return [Class]
|
277
|
+
attr_accessor :type
|
223
278
|
|
224
|
-
# @return [
|
225
|
-
def add_member(name,
|
226
|
-
member = MemberShape.new(member_name, shape, traits: traits)
|
227
|
-
@members[name] = member
|
228
|
-
@names_by_member_name[member_name] = name
|
279
|
+
# @return [ShapeRef]
|
280
|
+
def add_member(name, type, shape_ref)
|
229
281
|
@member_types[name] = type
|
230
|
-
@members_by_type[type] =
|
231
|
-
|
282
|
+
@members_by_type[type] = [name, shape_ref]
|
283
|
+
@members[name] = shape_ref
|
284
|
+
end
|
285
|
+
|
286
|
+
# @param [Symbol] name
|
287
|
+
# @return [Boolean]
|
288
|
+
def member?(name)
|
289
|
+
@members.key?(name)
|
290
|
+
end
|
291
|
+
|
292
|
+
# @param [Symbol] name
|
293
|
+
# @return [ShapeRef, nil]
|
294
|
+
def member(name)
|
295
|
+
@members[name]
|
232
296
|
end
|
233
297
|
|
234
298
|
# @param [Symbol] name
|
@@ -250,30 +314,12 @@ module Smithy
|
|
250
314
|
end
|
251
315
|
|
252
316
|
# @param [Class] type
|
253
|
-
# @return [
|
317
|
+
# @return [ShapeRef, nil]
|
254
318
|
def member_by_type(type)
|
255
319
|
@members_by_type[type]
|
256
320
|
end
|
257
321
|
end
|
258
322
|
|
259
|
-
# Represents a member shape.
|
260
|
-
class MemberShape
|
261
|
-
def initialize(name, shape, traits: {})
|
262
|
-
@name = name
|
263
|
-
@shape = shape
|
264
|
-
@traits = traits
|
265
|
-
end
|
266
|
-
|
267
|
-
# @return [String] Member name
|
268
|
-
attr_accessor :name
|
269
|
-
|
270
|
-
# @return [Shape] Referenced shape
|
271
|
-
attr_accessor :shape
|
272
|
-
|
273
|
-
# @return [Hash<String, Object>]
|
274
|
-
attr_accessor :traits
|
275
|
-
end
|
276
|
-
|
277
323
|
# Prelude shape definitions.
|
278
324
|
module Prelude
|
279
325
|
BigDecimal = BigDecimalShape.new(id: 'smithy.api#BigDecimal')
|
@@ -321,6 +367,7 @@ module Smithy
|
|
321
367
|
id: 'smithy.api#Unit',
|
322
368
|
traits: { 'smithy.api#unitType' => {} }
|
323
369
|
)
|
370
|
+
Unit.type = Schema::EmptyStructure
|
324
371
|
end
|
325
372
|
end
|
326
373
|
end
|
@@ -2,15 +2,13 @@
|
|
2
2
|
|
3
3
|
module Smithy
|
4
4
|
module Schema
|
5
|
-
# A module mixed into Structs that provides utility methods.
|
5
|
+
# A module mixed into Structs that provides utility methods for Structure shapes.
|
6
6
|
module Structure
|
7
7
|
# Deeply converts the Struct into a hash. Structure members that
|
8
8
|
# are `nil` are omitted from the resultant hash.
|
9
9
|
# @return [Hash, Structure]
|
10
10
|
def to_h(obj = self)
|
11
11
|
case obj
|
12
|
-
when Union
|
13
|
-
obj.to_h
|
14
12
|
when Structure
|
15
13
|
_to_h_structure(obj)
|
16
14
|
when Hash
|
@@ -42,10 +40,5 @@ module Smithy
|
|
42
40
|
obj.collect { |value| to_hash(value) }
|
43
41
|
end
|
44
42
|
end
|
45
|
-
|
46
|
-
# An empty Struct that includes the {Client::Structure} module.
|
47
|
-
EmptyStructure = Struct.new do
|
48
|
-
include Smithy::Schema::Structure
|
49
|
-
end
|
50
43
|
end
|
51
44
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Schema
|
5
|
+
# A registry that contains a map of Smithy shape ID to its shape defined in a schema.
|
6
|
+
# The registered shapes are limited to {Shapes::StructureShape} with a type.
|
7
|
+
#
|
8
|
+
# This registry has the following functionalities:
|
9
|
+
#
|
10
|
+
# * Access shape by shape ID
|
11
|
+
# * Access shape by its type
|
12
|
+
# * Register shape to the Registry
|
13
|
+
# * Supports enumeration of registered shapes
|
14
|
+
#
|
15
|
+
# You could also combine multiple registries into one {TypeRegistry}.
|
16
|
+
#
|
17
|
+
# @example Creating a new Registry
|
18
|
+
# # accepts an array of structure shapes
|
19
|
+
# registry = TypeRegistry.new(StructureShape1, StructureShape2)
|
20
|
+
#
|
21
|
+
# @example Shape Lookup
|
22
|
+
# # Find shape by its id
|
23
|
+
# registry["someId"]
|
24
|
+
# # => #<Smithy::Schema::Shapes::StructureShape...>
|
25
|
+
#
|
26
|
+
# # Find shape by its type
|
27
|
+
# registry.shape_by_type(ExampleService::Types::Structure)
|
28
|
+
# # => #<Smithy::Schema::Shapes::StructureShape...>
|
29
|
+
#
|
30
|
+
# @example Combining multiple registries
|
31
|
+
# registry.concat(registry1, registry2)
|
32
|
+
# # => #<Smithy::Schema::TypeRegistry...>
|
33
|
+
class TypeRegistry
|
34
|
+
include Enumerable
|
35
|
+
|
36
|
+
# @param [Array<Shapes::StructureShape>] shapes
|
37
|
+
def initialize(shapes = [])
|
38
|
+
@registry = {}
|
39
|
+
@shapes_by_type = {}
|
40
|
+
shapes.each do |shape|
|
41
|
+
self[shape.id] = shape
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def each(&)
|
46
|
+
@registry.each(&)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param [String] id
|
50
|
+
# @return [Shapes::StructureShape, nil]
|
51
|
+
def [](id)
|
52
|
+
@registry[id]
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param [String] id
|
56
|
+
# @param [Shapes::StructureShape] shape
|
57
|
+
def []=(id, shape)
|
58
|
+
validate_shape(shape)
|
59
|
+
@registry[id] = shape
|
60
|
+
@shapes_by_type[shape.type] = shape
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Boolean]
|
64
|
+
def empty?
|
65
|
+
@registry.empty?
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param [String] id
|
69
|
+
def key?(id)
|
70
|
+
@registry.key?(id)
|
71
|
+
end
|
72
|
+
alias include? key?
|
73
|
+
|
74
|
+
# @return [Array<String>]
|
75
|
+
def keys
|
76
|
+
@registry.keys
|
77
|
+
end
|
78
|
+
|
79
|
+
# @param [Class] type
|
80
|
+
def shape_by_type?(type)
|
81
|
+
@shapes_by_type.key?(type)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param [Class] type
|
85
|
+
# @return [Shapes::StructureShape, nil]
|
86
|
+
def shape_by_type(type)
|
87
|
+
@shapes_by_type[type]
|
88
|
+
end
|
89
|
+
|
90
|
+
# @return [Array<Shape::StructureShape>]
|
91
|
+
def values
|
92
|
+
@registry.values
|
93
|
+
end
|
94
|
+
|
95
|
+
# Merges multiple type registries into a new registry.
|
96
|
+
#
|
97
|
+
# @param [Array<TypeRegistry>] type_registries
|
98
|
+
# @return [TypeRegistry]
|
99
|
+
def merge(*type_registries)
|
100
|
+
registry = TypeRegistry.new
|
101
|
+
@registry.each do |shape_id, shape|
|
102
|
+
registry[shape_id] = shape
|
103
|
+
end
|
104
|
+
type_registries.each do |type_registry|
|
105
|
+
unless type_registry.is_a?(TypeRegistry)
|
106
|
+
raise ArgumentError, "expected TypeRegistry, got #{type_registry.class}"
|
107
|
+
end
|
108
|
+
|
109
|
+
type_registry.each do |shape_id, shape|
|
110
|
+
registry[shape_id] = shape
|
111
|
+
end
|
112
|
+
end
|
113
|
+
registry
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def validate_shape(shape)
|
119
|
+
return if shape.is_a?(Shapes::StructureShape) && shape.type
|
120
|
+
|
121
|
+
raise ArgumentError, "expected a StructureShape with a type, got: #{shape.class}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/smithy-schema/union.rb
CHANGED
@@ -4,16 +4,16 @@ require 'delegate'
|
|
4
4
|
|
5
5
|
module Smithy
|
6
6
|
module Schema
|
7
|
-
#
|
8
|
-
|
7
|
+
# A module mixed into Structs that provides utility methods for Union shapes.
|
8
|
+
module Union
|
9
9
|
include Structure
|
10
10
|
|
11
|
-
def
|
12
|
-
|
11
|
+
def member
|
12
|
+
members.find { |m| !self[m].nil? }
|
13
13
|
end
|
14
14
|
|
15
15
|
def value
|
16
|
-
|
16
|
+
self[member] if member
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/lib/smithy-schema.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'smithy-schema/shapes'
|
4
3
|
require_relative 'smithy-schema/structure'
|
4
|
+
require_relative 'smithy-schema/empty_structure'
|
5
5
|
require_relative 'smithy-schema/union'
|
6
6
|
|
7
|
+
require_relative 'smithy-schema/shapes'
|
8
|
+
require_relative 'smithy-schema/document'
|
9
|
+
require_relative 'smithy-schema/type_registry'
|
10
|
+
|
7
11
|
module Smithy
|
8
|
-
# Base module for Smithy
|
9
|
-
module
|
12
|
+
# Base module for Smithy schema classes.
|
13
|
+
module Schema
|
10
14
|
VERSION = File.read(File.expand_path('../VERSION', __dir__.to_s)).strip
|
11
15
|
end
|
12
16
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smithy-schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Amazon Web Services
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-06-26 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
description: Smithy is a code generation toolkit for creating Client and Server SDKs
|
13
13
|
from Smithy models.
|
@@ -18,8 +18,13 @@ files:
|
|
18
18
|
- CHANGELOG.md
|
19
19
|
- VERSION
|
20
20
|
- lib/smithy-schema.rb
|
21
|
+
- lib/smithy-schema/document.rb
|
22
|
+
- lib/smithy-schema/document_utils/deserializer.rb
|
23
|
+
- lib/smithy-schema/document_utils/serializer.rb
|
24
|
+
- lib/smithy-schema/empty_structure.rb
|
21
25
|
- lib/smithy-schema/shapes.rb
|
22
26
|
- lib/smithy-schema/structure.rb
|
27
|
+
- lib/smithy-schema/type_registry.rb
|
23
28
|
- lib/smithy-schema/union.rb
|
24
29
|
homepage: https://github.com/smithy-lang/smithy-ruby
|
25
30
|
licenses:
|