smithy-json 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7d10d334459b5725b2db3bff1f85606f48557787d60c130b850e1aef10c77b3d
4
+ data.tar.gz: 43dde2a55c584ebfb576489bd654a03d8c92e36f13fddc7acabcf1ca313009bc
5
+ SHA512:
6
+ metadata.gz: 15f40462768f4e294e86b382ca91adfbab7f038c335f65a2df76a60fd143434d64a64d4daadf574bc294bd5f37b338988ff216983289d87566bc4ed4d85e1e97
7
+ data.tar.gz: 2e85d7bfa40a407212592cf6df0404188b2e83ca751781f6e4873ebbf99750e063f457746b9942e1f5ada9a6ddb8a3077bd8394b16c23370025b09e77000b805
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ Unreleased Changes
2
+ ------------------
3
+
4
+ * Feature - Initial version of this gem.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0.pre1
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'deserializer'
4
+ require_relative 'serializer'
5
+
6
+ module Smithy
7
+ module JSON
8
+ # @api private
9
+ class Codec
10
+ # @param [Hash] options
11
+ def initialize(options = {})
12
+ @options = options
13
+ end
14
+
15
+ # @param [Shape] shape
16
+ # @param [Object] data
17
+ # @return [String, nil]
18
+ def serialize(shape, data)
19
+ Serializer.new(@options).serialize(shape, data)
20
+ end
21
+
22
+ # @param [Shape] shape
23
+ # @param [String] bytes
24
+ # @param [Struct] target
25
+ # @return [Object, nil]
26
+ def deserialize(shape, bytes, target = nil)
27
+ Deserializer.new(@options).deserialize(shape, bytes, target)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Smithy
6
+ module JSON
7
+ # @api private
8
+ class Deserializer
9
+ include Smithy::Schema::Shapes
10
+
11
+ def initialize(options = {})
12
+ @json_name = options[:json_name] || false
13
+ end
14
+
15
+ def deserialize(shape, bytes, target)
16
+ return {} if bytes.empty?
17
+
18
+ ref = shape.is_a?(ShapeRef) ? shape : ShapeRef.new(shape: shape)
19
+ shape(ref, Smithy::JSON.load(bytes), target)
20
+ end
21
+
22
+ private
23
+
24
+ def shape(ref, value, target = nil) # rubocop:disable Metrics/CyclomaticComplexity
25
+ case ref.shape
26
+ when BlobShape then Base64.decode64(value)
27
+ when FloatShape then float(value)
28
+ when ListShape then list(ref, value, target)
29
+ when MapShape then map(ref, value, target)
30
+ when StructureShape then structure(ref, value, target)
31
+ when TimestampShape then timestamp(value)
32
+ when UnionShape then union(ref, value, target)
33
+ else value
34
+ end
35
+ end
36
+
37
+ def float(value)
38
+ case value
39
+ when 'Infinity' then ::Float::INFINITY
40
+ when '-Infinity' then -::Float::INFINITY
41
+ when 'NaN' then ::Float::NAN
42
+ when nil then nil
43
+ else value.to_f
44
+ end
45
+ end
46
+
47
+ def list(ref, values, target = nil)
48
+ return if values.nil?
49
+
50
+ target = [] if target.nil?
51
+ values.each do |value|
52
+ next if value.nil? && !sparse?(ref.shape)
53
+
54
+ target << shape(ref.shape.member, value)
55
+ end
56
+ target
57
+ end
58
+
59
+ def map(ref, values, target = nil)
60
+ target = {} if target.nil?
61
+ values.each do |key, value|
62
+ next if value.nil? && !sparse?(ref.shape)
63
+
64
+ target[key] = shape(ref.shape.value, value)
65
+ end
66
+ target
67
+ end
68
+
69
+ def structure(ref, values, target = nil)
70
+ return if values.nil?
71
+
72
+ target = ref.shape.type.new if target.nil?
73
+ ref.shape.members.each do |member_name, member_ref|
74
+ value = values[location_name(member_ref)]
75
+ target[member_name] = shape(member_ref, value) unless value.nil?
76
+ end
77
+ target
78
+ end
79
+
80
+ def timestamp(value)
81
+ case value
82
+ when nil then nil
83
+ when Numeric then Time.at(value)
84
+ when /^[\d.]+$/ then Time.at(value.to_f)
85
+ else
86
+ begin
87
+ fractional_time = Time.parse(value).to_f
88
+ Time.at(fractional_time).utc
89
+ rescue ArgumentError
90
+ raise "unhandled timestamp format: #{value}"
91
+ end
92
+ end
93
+ end
94
+
95
+ def union(ref, values, target = nil) # rubocop:disable Metrics/AbcSize
96
+ ref.shape.members.each do |member_name, member_ref|
97
+ value = values[location_name(member_ref)]
98
+ next if value.nil?
99
+
100
+ target = ref.shape.member_type(member_name) if target.nil?
101
+ return target.new(member_name => shape(member_ref, value))
102
+ end
103
+
104
+ values.delete('__type')
105
+ key, value = values.first
106
+ ref.shape.member_type(:unknown).new(unknown: { key => value })
107
+ end
108
+
109
+ def location_name(ref)
110
+ return ref.member_name unless @json_name
111
+
112
+ ref.traits['smithy.api#jsonName'] || ref.member_name
113
+ end
114
+
115
+ def sparse?(shape)
116
+ shape.traits.key?('smithy.api#sparse')
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Smithy
6
+ module JSON
7
+ # @api private
8
+ module JsonEngine
9
+ class << self
10
+ def load(json)
11
+ ::JSON.parse(json)
12
+ rescue ::JSON::ParserError => e
13
+ raise ParseError, e
14
+ end
15
+
16
+ def dump(value)
17
+ ::JSON.dump(value)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj'
4
+
5
+ module Smithy
6
+ module JSON
7
+ # @api private
8
+ module OjEngine
9
+ class << self
10
+ def load(json)
11
+ Oj.load(json, mode: :compat, symbol_keys: false)
12
+ rescue Oj::ParseError, EncodingError => e
13
+ raise ParseError, e
14
+ end
15
+
16
+ def dump(value)
17
+ Oj.dump(value, mode: :compat)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Smithy
6
+ module JSON
7
+ # @api private
8
+ class Serializer
9
+ include Smithy::Schema::Shapes
10
+
11
+ def initialize(options = {})
12
+ @json_name = options[:json_name] || false
13
+ end
14
+
15
+ def serialize(shape, data)
16
+ ref = shape.is_a?(ShapeRef) ? shape : ShapeRef.new(shape: shape)
17
+ Smithy::JSON.dump(shape(ref, data))
18
+ end
19
+
20
+ private
21
+
22
+ def shape(ref, value) # rubocop:disable Metrics/CyclomaticComplexity
23
+ shape = ref.shape
24
+ case shape
25
+ when BlobShape then blob(value)
26
+ when FloatShape then float(value)
27
+ when ListShape then list(ref, value)
28
+ when MapShape then map(ref, value)
29
+ when StructureShape then structure(ref, value)
30
+ when TimestampShape then timestamp(ref, value)
31
+ when UnionShape then union(ref, value)
32
+ else value
33
+ end
34
+ end
35
+
36
+ def blob(value)
37
+ Base64.strict_encode64(value.respond_to?(:read) ? value.read : value)
38
+ end
39
+
40
+ def float(value)
41
+ if value == ::Float::INFINITY
42
+ 'Infinity'
43
+ elsif value == -::Float::INFINITY
44
+ '-Infinity'
45
+ elsif value.nan?
46
+ 'NaN'
47
+ else
48
+ value
49
+ end
50
+ end
51
+
52
+ def list(ref, values)
53
+ return if values.nil?
54
+
55
+ shape = ref.shape
56
+ values.collect do |value|
57
+ shape(shape.member, value)
58
+ end
59
+ end
60
+
61
+ def map(ref, values)
62
+ return if values.nil?
63
+
64
+ shape = ref.shape
65
+ values.each.with_object({}) do |(key, value), data|
66
+ data[key] = shape(shape.value, value)
67
+ end
68
+ end
69
+
70
+ def structure(ref, values)
71
+ return if values.nil?
72
+
73
+ ref.shape.members.each_with_object({}) do |(member_name, member_ref), data|
74
+ value = values[member_name]
75
+ data[location_name(member_ref)] = shape(member_ref, value) unless value.nil?
76
+ end
77
+ end
78
+
79
+ def timestamp(ref, value)
80
+ trait = 'smithy.api#timestampFormat'
81
+ case ref.traits[trait] || ref.shape.traits[trait]
82
+ when 'date-time' then value.utc.iso8601
83
+ when 'http-date' then value.utc.httpdate
84
+ else
85
+ # default to epoch-seconds
86
+ value.to_i
87
+ end
88
+ end
89
+
90
+ def union(ref, values) # rubocop:disable Metrics/AbcSize
91
+ return if values.nil?
92
+
93
+ data = {}
94
+ if values.is_a?(Schema::Union)
95
+ _name, member_ref = ref.shape.member_by_type(values.class)
96
+ data[location_name(member_ref)] = shape(member_ref, values.value)
97
+ else
98
+ key, value = values.first
99
+ if ref.shape.member?(key)
100
+ member_ref = ref.shape.member(key)
101
+ data[location_name(member_ref)] = shape(member_ref, value)
102
+ end
103
+ end
104
+ data
105
+ end
106
+
107
+ def location_name(ref)
108
+ return ref.member_name unless @json_name
109
+
110
+ ref.traits['smithy.api#jsonName'] || ref.member_name
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'smithy-schema'
4
+
5
+ require_relative 'smithy-json/codec'
6
+
7
+ module Smithy
8
+ # Smithy::JSON is a purpose-built set of utilities for working with JSON.
9
+ module JSON
10
+ VERSION = File.read(File.expand_path('../VERSION', __dir__.to_s)).strip
11
+
12
+ # Raised when a JSON parsing error occurs.
13
+ class ParseError < StandardError
14
+ def initialize(error)
15
+ @error = error
16
+ super(error.message)
17
+ end
18
+
19
+ attr_reader :error
20
+ end
21
+
22
+ class << self
23
+ # @param [Symbol,Class] engine
24
+ # Must be one of the following values:
25
+ #
26
+ # * :oj
27
+ # * :json
28
+ #
29
+ def engine=(engine)
30
+ @engine = engine.is_a?(Class) ? engine : load_engine(engine)
31
+ end
32
+
33
+ # @return [Class] Returns the default engine.
34
+ # One of:
35
+ #
36
+ # * {OjEngine}
37
+ # * {JsonEngine}
38
+ #
39
+ def engine
40
+ set_default_engine unless @engine
41
+ @engine
42
+ end
43
+
44
+ def load(json)
45
+ @engine.load(json)
46
+ end
47
+
48
+ def dump(value)
49
+ @engine.dump(value)
50
+ end
51
+
52
+ def set_default_engine
53
+ %i[oj json].each do |name|
54
+ @engine ||= try_load_engine(name)
55
+ end
56
+ return if @engine
57
+
58
+ raise 'Unable to find a compatible json library. ' \
59
+ 'Ensure that you have installed or added to your Gemfile one of: oj or json'
60
+ end
61
+
62
+ private
63
+
64
+ def try_load_engine(name)
65
+ load_engine(name)
66
+ rescue LoadError
67
+ nil
68
+ end
69
+
70
+ def load_engine(name)
71
+ require "smithy-json/#{name}_engine"
72
+ const_name = "#{name[0].upcase}#{name[1..]}Engine"
73
+ const_get(const_name)
74
+ end
75
+ end
76
+
77
+ set_default_engine
78
+ end
79
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smithy-json
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre1
5
+ platform: ruby
6
+ authors:
7
+ - Amazon Web Services
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-06-26 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: smithy-schema
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - '='
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.0.pre1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '='
38
+ - !ruby/object:Gem::Version
39
+ version: 1.0.0.pre1
40
+ description: Smithy is a code generation toolkit for creating Client and Server SDKs
41
+ from Smithy models.
42
+ executables: []
43
+ extensions: []
44
+ extra_rdoc_files: []
45
+ files:
46
+ - CHANGELOG.md
47
+ - VERSION
48
+ - lib/smithy-json.rb
49
+ - lib/smithy-json/codec.rb
50
+ - lib/smithy-json/deserializer.rb
51
+ - lib/smithy-json/json_engine.rb
52
+ - lib/smithy-json/oj_engine.rb
53
+ - lib/smithy-json/serializer.rb
54
+ homepage: https://github.com/smithy-lang/smithy-ruby
55
+ licenses:
56
+ - Apache-2.0
57
+ metadata: {}
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '3.3'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubygems_version: 3.6.2
73
+ specification_version: 4
74
+ summary: JSON utilities and codec for Smithy generated clients and servers
75
+ test_files: []