Skip to content

Commit 4edfae3

Browse files
committed
Expand on typed docs
1 parent a61318f commit 4edfae3

3 files changed

Lines changed: 188 additions & 162 deletions

File tree

gems/smithy-schema/lib/smithy-schema/document.rb

Lines changed: 13 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,37 @@
11
# frozen_string_literal: true
22

3-
require 'json'
3+
require_relative 'documents'
44

55
module Smithy
66
module Schema
77
# TODO: need to address the following and more
88
# * documentation
9-
# * some json considerations like timestamp, jsonName trait etc
10-
# * handle union stuffs
119
class Document
10+
include Documents
1211
def initialize(data, schema = nil)
13-
@data = format_data(data, schema) # ruby obj to make it easy to work with
14-
@discriminator = schema ? extract_discriminator(data, schema) : nil
12+
@data = format_data(data, schema)
13+
@discriminator = Extractor.discriminator(data, schema)
14+
@schema = schema
1515
end
1616

17-
attr_reader :data, :discriminator
18-
19-
# if discriminator is set, add discriminator being generating
20-
def as_json
21-
JSON.generate(@data, allow_nan: true) # do we want to allow this?
22-
end
17+
attr_reader :data, :discriminator, :schema
2318

2419
def [](key)
2520
return unless @data.is_a?(Hash) && @data.key?(key)
2621

2722
@data[key]
2823
end
2924

30-
# expected schema here is a shape that has a type representation
3125
def as_typed(schema)
32-
# ensures that given schema has a type representation and
3326
error_message = 'Invalid schema or document data'
3427
raise ArgumentError, error_message unless valid_schema?(schema) && @data.is_a?(Hash)
3528

3629
type = schema.type.new
37-
apply_data(schema, @data, type)
30+
Applier.apply(schema, @data, type)
3831
end
3932

4033
private
4134

42-
def apply_data(schema, data, type = nil)
43-
case schema
44-
when Shapes::StructureShape then apply_structure(schema, data, type)
45-
# when Shapes::UnionShape then union(shape, value, type)
46-
when Shapes::ListShape then apply_list(schema, data)
47-
when Shapes::MapShape then apply_map(schema, data)
48-
else data
49-
end
50-
end
51-
52-
def apply_structure(schema, data, type)
53-
type = schema.type.new if type.nil?
54-
data.each do |k, v|
55-
next if (name = resolve_member_name(schema, k)).nil?
56-
57-
type[name] = apply_data(schema.member(name).shape, v)
58-
end
59-
type
60-
end
61-
62-
def resolve_member_name(schema, key)
63-
return unless schema.name_by_member_name?(key) || schema.member?(key.to_sym)
64-
65-
schema.name_by_member_name(key) || key.to_sym
66-
end
67-
68-
def apply_list(schema, data)
69-
data.map do |v|
70-
next if v.nil?
71-
72-
apply_data(schema.member.shape, v)
73-
end
74-
end
75-
76-
def apply_map(schema, data)
77-
data.each_with_object({}) do |(k, v), h|
78-
h[k.to_s] =
79-
if v.nil?
80-
nil
81-
else
82-
apply_data(schema.value.shape, v)
83-
end
84-
end
85-
end
86-
87-
def valid_schema?(schema)
88-
schema.is_a?(Shapes::StructureShape) && !schema.type.nil?
89-
end
90-
9135
def discriminator?(data)
9236
data.is_a?(Hash) && data.key?('__type')
9337
end
@@ -96,63 +40,20 @@ def format_data(data, schema)
9640
return if data.nil?
9741

9842
case data
99-
when Smithy::Schema::Structure # indicates that this is a runtime shape
43+
when Smithy::Schema::Structure
10044
if schema.nil? || !schema.is_a?(Shapes::StructureShape)
10145
raise ArgumentError, 'Unable to convert as document with given schema'
10246
end
10347

104-
extract_data(schema, data)
48+
Extractor.extract(schema, data)
10549
else
106-
data
107-
end
108-
end
109-
110-
# handle timestamp, union, number?
111-
def extract_data(schema, data)
112-
return nil if data.nil?
113-
114-
case schema
115-
when Shapes::StructureShape then extract_structure(schema, data)
116-
when Shapes::ListShape then extract_list(schema, data)
117-
when Shapes::MapShape then extract_map(schema, data)
118-
when Shapes::BlobShape then extract_blob(data)
119-
else data
120-
end
121-
end
122-
123-
def extract_structure(schema, data)
124-
data.to_h.each_with_object({}) do |(k, v), o|
125-
next unless schema.member?(k)
126-
127-
member_shape = schema.member(k)
128-
o[member_shape.name] = extract_data(member_shape.shape, v)
129-
end
130-
end
131-
132-
def extract_list(schema, data)
133-
data.collect { |value| extract_data(schema.member.shape, value) }
134-
end
135-
136-
def extract_map(schema, data)
137-
data.each.with_object({}) do |(k, v), h|
138-
h[k.to_s] = extract_data(schema.value.shape, v)
50+
data = data.except('__type') if discriminator?(data)
51+
data # TODO: add some validation if schema exists
13952
end
14053
end
14154

142-
def extract_blob(data)
143-
Base64.strict_encode64(data.is_a?(String) ? data : data.read)
144-
end
145-
146-
def extract_discriminator(data, schema)
147-
return if data.nil?
148-
149-
if discriminator?(data)
150-
data['__type']
151-
elsif schema
152-
raise "Expected a structure schema, given #{schema} instead" unless schema.is_a?(Shapes::Shape)
153-
154-
schema.id
155-
end
55+
def valid_schema?(schema)
56+
schema.is_a?(Shapes::StructureShape) && !schema.type.nil?
15657
end
15758
end
15859
end
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# frozen_string_literal: true
2+
3+
module Smithy
4+
module Schema
5+
module Documents
6+
# Contains methods to extract given data as document data
7+
module Extractor
8+
class << self
9+
def extract(schema, data)
10+
return nil if data.nil?
11+
12+
case schema
13+
when Shapes::StructureShape then extract_structure(schema, data)
14+
when Shapes::UnionShape then extract_union(schema, data)
15+
when Shapes::ListShape then extract_list(schema, data)
16+
when Shapes::MapShape then extract_map(schema, data)
17+
else data
18+
end
19+
end
20+
21+
def extract_structure(schema, data)
22+
data.to_h.each_with_object({}) do |(k, v), o|
23+
next unless schema.member?(k)
24+
25+
o[k] = extract(schema.member(k).shape, v)
26+
end
27+
end
28+
29+
def extract_union(schema, data)
30+
output = {}
31+
if data.is_a?(Schema::Union)
32+
member_shape = schema.member_by_type(data.class)
33+
output[member_shape.name] = extract(member_shape.shape, data).value
34+
else
35+
key, value = data.first
36+
if schema.member?(key)
37+
member_shape = schema.member(key)
38+
output[member_shape.name] = extract(member_shape.shape, value)
39+
end
40+
end
41+
output
42+
end
43+
44+
def extract_list(schema, data)
45+
data.collect { |v| extract(schema.member.shape, v) }
46+
end
47+
48+
def extract_map(schema, data)
49+
data.each.with_object({}) do |(k, v), h|
50+
h[k] = extract(schema.value.shape, v)
51+
end
52+
end
53+
54+
def discriminator(data, schema)
55+
return if data.nil?
56+
57+
if discriminator?(data)
58+
data['__type']
59+
elsif schema
60+
unless schema.is_a?(Shapes::StructureShape)
61+
raise "Expected a structure schema, given #{schema.class} instead"
62+
end
63+
64+
schema.id
65+
end
66+
end
67+
68+
def discriminator?(data)
69+
data.is_a?(Hash) && data.key?('__type')
70+
end
71+
end
72+
end
73+
74+
# Contains methods to apply document data to runtime shapes
75+
module Applier
76+
class << self
77+
def apply(schema, data, type = nil)
78+
case schema
79+
when Shapes::StructureShape then apply_structure(schema, data, type)
80+
when Shapes::UnionShape then apply_union(schema, data, type)
81+
when Shapes::ListShape then apply_list(schema, data)
82+
when Shapes::MapShape then apply_map(schema, data)
83+
else data
84+
end
85+
end
86+
87+
def apply_structure(schema, data, type)
88+
type = schema.type.new if type.nil?
89+
data.each do |k, v|
90+
next if (name = resolve_member_name(schema, k)).nil?
91+
92+
type[name] = apply(schema.member(name).shape, v)
93+
end
94+
type
95+
end
96+
97+
def apply_union(schema, data, type)
98+
key, value = data.flatten
99+
return if key.nil?
100+
101+
if schema.name_by_member_name?(key)
102+
member_name = schema.name_by_member_name(key)
103+
type = schema.member_type(member_name) if type.nil?
104+
type.new(apply(schema.member(member_name).shape, value))
105+
else
106+
schema.member_type(:unknown).new(key, value)
107+
end
108+
end
109+
110+
def apply_list(schema, data)
111+
data.map do |v|
112+
next if v.nil?
113+
114+
apply(schema.member.shape, v)
115+
end
116+
end
117+
118+
def apply_map(schema, data)
119+
data.transform_values do |v|
120+
if v.nil?
121+
nil
122+
else
123+
apply(schema.value.shape, v)
124+
end
125+
end
126+
end
127+
128+
private
129+
130+
def resolve_member_name(schema, key)
131+
return unless schema.name_by_member_name?(key) || schema.member?(key.to_sym)
132+
133+
schema.name_by_member_name(key) || key.to_sym
134+
end
135+
end
136+
end
137+
end
138+
end
139+
end

0 commit comments

Comments
 (0)