apiwork 0.0.0.pre → 0.1.1

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.
Files changed (202) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +2 -2
  3. data/README.md +117 -1
  4. data/Rakefile +5 -3
  5. data/app/controllers/apiwork/errors_controller.rb +13 -0
  6. data/app/controllers/apiwork/exports_controller.rb +22 -0
  7. data/lib/apiwork/abstractable.rb +26 -0
  8. data/lib/apiwork/adapter/base.rb +369 -0
  9. data/lib/apiwork/adapter/builder/api/base.rb +66 -0
  10. data/lib/apiwork/adapter/builder/contract/base.rb +86 -0
  11. data/lib/apiwork/adapter/capability/api/base.rb +51 -0
  12. data/lib/apiwork/adapter/capability/api/scope.rb +64 -0
  13. data/lib/apiwork/adapter/capability/base.rb +291 -0
  14. data/lib/apiwork/adapter/capability/contract/base.rb +37 -0
  15. data/lib/apiwork/adapter/capability/contract/scope.rb +110 -0
  16. data/lib/apiwork/adapter/capability/operation/base.rb +172 -0
  17. data/lib/apiwork/adapter/capability/operation/metadata_shape.rb +165 -0
  18. data/lib/apiwork/adapter/capability/result.rb +21 -0
  19. data/lib/apiwork/adapter/capability/runner.rb +56 -0
  20. data/lib/apiwork/adapter/capability/transformer/request/base.rb +72 -0
  21. data/lib/apiwork/adapter/capability/transformer/response/base.rb +45 -0
  22. data/lib/apiwork/adapter/registry.rb +16 -0
  23. data/lib/apiwork/adapter/serializer/error/base.rb +72 -0
  24. data/lib/apiwork/adapter/serializer/error/default/api_builder.rb +32 -0
  25. data/lib/apiwork/adapter/serializer/error/default.rb +37 -0
  26. data/lib/apiwork/adapter/serializer/resource/base.rb +84 -0
  27. data/lib/apiwork/adapter/serializer/resource/default/contract_builder.rb +209 -0
  28. data/lib/apiwork/adapter/serializer/resource/default.rb +39 -0
  29. data/lib/apiwork/adapter/standard/capability/filtering/api_builder.rb +75 -0
  30. data/lib/apiwork/adapter/standard/capability/filtering/constants.rb +37 -0
  31. data/lib/apiwork/adapter/standard/capability/filtering/contract_builder.rb +193 -0
  32. data/lib/apiwork/adapter/standard/capability/filtering/operation/filter/builder.rb +47 -0
  33. data/lib/apiwork/adapter/standard/capability/filtering/operation/filter/operator_builder.rb +36 -0
  34. data/lib/apiwork/adapter/standard/capability/filtering/operation/filter.rb +462 -0
  35. data/lib/apiwork/adapter/standard/capability/filtering/operation.rb +22 -0
  36. data/lib/apiwork/adapter/standard/capability/filtering/request_transformer.rb +47 -0
  37. data/lib/apiwork/adapter/standard/capability/filtering.rb +18 -0
  38. data/lib/apiwork/adapter/standard/capability/including/contract_builder.rb +169 -0
  39. data/lib/apiwork/adapter/standard/capability/including/operation.rb +20 -0
  40. data/lib/apiwork/adapter/standard/capability/including.rb +16 -0
  41. data/lib/apiwork/adapter/standard/capability/pagination/api_builder.rb +34 -0
  42. data/lib/apiwork/adapter/standard/capability/pagination/contract_builder.rb +35 -0
  43. data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate/cursor.rb +84 -0
  44. data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate/offset.rb +66 -0
  45. data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate.rb +24 -0
  46. data/lib/apiwork/adapter/standard/capability/pagination/operation.rb +24 -0
  47. data/lib/apiwork/adapter/standard/capability/pagination.rb +21 -0
  48. data/lib/apiwork/adapter/standard/capability/sorting/api_builder.rb +19 -0
  49. data/lib/apiwork/adapter/standard/capability/sorting/contract_builder.rb +84 -0
  50. data/lib/apiwork/adapter/standard/capability/sorting/operation/sort.rb +83 -0
  51. data/lib/apiwork/adapter/standard/capability/sorting/operation.rb +22 -0
  52. data/lib/apiwork/adapter/standard/capability/sorting.rb +17 -0
  53. data/lib/apiwork/adapter/standard/capability/writing/constants.rb +15 -0
  54. data/lib/apiwork/adapter/standard/capability/writing/contract_builder.rb +253 -0
  55. data/lib/apiwork/adapter/standard/capability/writing/operation/issue_mapper.rb +210 -0
  56. data/lib/apiwork/adapter/standard/capability/writing/operation.rb +32 -0
  57. data/lib/apiwork/adapter/standard/capability/writing/request_transformer.rb +37 -0
  58. data/lib/apiwork/adapter/standard/capability/writing.rb +17 -0
  59. data/lib/apiwork/adapter/standard/includes_resolver.rb +106 -0
  60. data/lib/apiwork/adapter/standard.rb +22 -0
  61. data/lib/apiwork/adapter/wrapper/base.rb +70 -0
  62. data/lib/apiwork/adapter/wrapper/collection/base.rb +60 -0
  63. data/lib/apiwork/adapter/wrapper/collection/default.rb +47 -0
  64. data/lib/apiwork/adapter/wrapper/error/base.rb +30 -0
  65. data/lib/apiwork/adapter/wrapper/error/default.rb +34 -0
  66. data/lib/apiwork/adapter/wrapper/member/base.rb +58 -0
  67. data/lib/apiwork/adapter/wrapper/member/default.rb +40 -0
  68. data/lib/apiwork/adapter/wrapper/shape.rb +203 -0
  69. data/lib/apiwork/adapter.rb +50 -0
  70. data/lib/apiwork/api/base.rb +802 -0
  71. data/lib/apiwork/api/element.rb +110 -0
  72. data/lib/apiwork/api/enum_registry/definition.rb +51 -0
  73. data/lib/apiwork/api/enum_registry.rb +98 -0
  74. data/lib/apiwork/api/info/contact.rb +67 -0
  75. data/lib/apiwork/api/info/license.rb +50 -0
  76. data/lib/apiwork/api/info/server.rb +50 -0
  77. data/lib/apiwork/api/info.rb +221 -0
  78. data/lib/apiwork/api/object.rb +235 -0
  79. data/lib/apiwork/api/registry.rb +33 -0
  80. data/lib/apiwork/api/representation_registry.rb +76 -0
  81. data/lib/apiwork/api/resource/action.rb +41 -0
  82. data/lib/apiwork/api/resource.rb +648 -0
  83. data/lib/apiwork/api/router.rb +104 -0
  84. data/lib/apiwork/api/type_registry/definition.rb +117 -0
  85. data/lib/apiwork/api/type_registry.rb +99 -0
  86. data/lib/apiwork/api/union.rb +49 -0
  87. data/lib/apiwork/api.rb +85 -0
  88. data/lib/apiwork/configurable.rb +71 -0
  89. data/lib/apiwork/configuration/option.rb +125 -0
  90. data/lib/apiwork/configuration/validatable.rb +25 -0
  91. data/lib/apiwork/configuration.rb +95 -0
  92. data/lib/apiwork/configuration_error.rb +6 -0
  93. data/lib/apiwork/constraint_error.rb +20 -0
  94. data/lib/apiwork/contract/action/request.rb +79 -0
  95. data/lib/apiwork/contract/action/response.rb +87 -0
  96. data/lib/apiwork/contract/action.rb +258 -0
  97. data/lib/apiwork/contract/base.rb +714 -0
  98. data/lib/apiwork/contract/element.rb +130 -0
  99. data/lib/apiwork/contract/object/coercer.rb +194 -0
  100. data/lib/apiwork/contract/object/deserializer.rb +101 -0
  101. data/lib/apiwork/contract/object/transformer.rb +95 -0
  102. data/lib/apiwork/contract/object/validator/result.rb +27 -0
  103. data/lib/apiwork/contract/object/validator.rb +734 -0
  104. data/lib/apiwork/contract/object.rb +566 -0
  105. data/lib/apiwork/contract/request_parser/result.rb +25 -0
  106. data/lib/apiwork/contract/request_parser.rb +72 -0
  107. data/lib/apiwork/contract/response_parser/result.rb +25 -0
  108. data/lib/apiwork/contract/response_parser.rb +35 -0
  109. data/lib/apiwork/contract/union.rb +56 -0
  110. data/lib/apiwork/contract_error.rb +9 -0
  111. data/lib/apiwork/controller.rb +300 -0
  112. data/lib/apiwork/domain_error.rb +13 -0
  113. data/lib/apiwork/element.rb +386 -0
  114. data/lib/apiwork/engine.rb +20 -0
  115. data/lib/apiwork/error.rb +6 -0
  116. data/lib/apiwork/error_code/definition.rb +63 -0
  117. data/lib/apiwork/error_code/registry.rb +18 -0
  118. data/lib/apiwork/error_code.rb +132 -0
  119. data/lib/apiwork/export/base.rb +291 -0
  120. data/lib/apiwork/export/open_api.rb +600 -0
  121. data/lib/apiwork/export/pipeline/writer.rb +66 -0
  122. data/lib/apiwork/export/pipeline.rb +84 -0
  123. data/lib/apiwork/export/registry.rb +16 -0
  124. data/lib/apiwork/export/surface_resolver.rb +189 -0
  125. data/lib/apiwork/export/type_analysis.rb +170 -0
  126. data/lib/apiwork/export/type_script.rb +23 -0
  127. data/lib/apiwork/export/type_script_mapper.rb +349 -0
  128. data/lib/apiwork/export/zod.rb +39 -0
  129. data/lib/apiwork/export/zod_mapper.rb +421 -0
  130. data/lib/apiwork/export.rb +80 -0
  131. data/lib/apiwork/http_error.rb +16 -0
  132. data/lib/apiwork/introspection/action/request.rb +66 -0
  133. data/lib/apiwork/introspection/action/response.rb +57 -0
  134. data/lib/apiwork/introspection/action.rb +124 -0
  135. data/lib/apiwork/introspection/api/info/contact.rb +59 -0
  136. data/lib/apiwork/introspection/api/info/license.rb +49 -0
  137. data/lib/apiwork/introspection/api/info/server.rb +50 -0
  138. data/lib/apiwork/introspection/api/info.rb +107 -0
  139. data/lib/apiwork/introspection/api/resource.rb +83 -0
  140. data/lib/apiwork/introspection/api.rb +92 -0
  141. data/lib/apiwork/introspection/contract.rb +63 -0
  142. data/lib/apiwork/introspection/dump/action.rb +101 -0
  143. data/lib/apiwork/introspection/dump/api.rb +119 -0
  144. data/lib/apiwork/introspection/dump/contract.rb +129 -0
  145. data/lib/apiwork/introspection/dump/param.rb +486 -0
  146. data/lib/apiwork/introspection/dump/resource.rb +112 -0
  147. data/lib/apiwork/introspection/dump/type.rb +339 -0
  148. data/lib/apiwork/introspection/dump.rb +17 -0
  149. data/lib/apiwork/introspection/enum.rb +63 -0
  150. data/lib/apiwork/introspection/error_code.rb +44 -0
  151. data/lib/apiwork/introspection/param/array.rb +88 -0
  152. data/lib/apiwork/introspection/param/base.rb +285 -0
  153. data/lib/apiwork/introspection/param/binary.rb +73 -0
  154. data/lib/apiwork/introspection/param/boolean.rb +73 -0
  155. data/lib/apiwork/introspection/param/date.rb +73 -0
  156. data/lib/apiwork/introspection/param/date_time.rb +73 -0
  157. data/lib/apiwork/introspection/param/decimal.rb +121 -0
  158. data/lib/apiwork/introspection/param/integer.rb +131 -0
  159. data/lib/apiwork/introspection/param/literal.rb +45 -0
  160. data/lib/apiwork/introspection/param/number.rb +121 -0
  161. data/lib/apiwork/introspection/param/object.rb +59 -0
  162. data/lib/apiwork/introspection/param/reference.rb +45 -0
  163. data/lib/apiwork/introspection/param/string.rb +122 -0
  164. data/lib/apiwork/introspection/param/time.rb +73 -0
  165. data/lib/apiwork/introspection/param/union.rb +57 -0
  166. data/lib/apiwork/introspection/param/unknown.rb +26 -0
  167. data/lib/apiwork/introspection/param/uuid.rb +73 -0
  168. data/lib/apiwork/introspection/param.rb +31 -0
  169. data/lib/apiwork/introspection/type.rb +129 -0
  170. data/lib/apiwork/introspection.rb +28 -0
  171. data/lib/apiwork/issue.rb +80 -0
  172. data/lib/apiwork/json_pointer.rb +21 -0
  173. data/lib/apiwork/object.rb +1618 -0
  174. data/lib/apiwork/reference_generator.rb +622 -0
  175. data/lib/apiwork/registry.rb +56 -0
  176. data/lib/apiwork/representation/association.rb +391 -0
  177. data/lib/apiwork/representation/attribute.rb +335 -0
  178. data/lib/apiwork/representation/base.rb +819 -0
  179. data/lib/apiwork/representation/deserializer.rb +95 -0
  180. data/lib/apiwork/representation/element.rb +128 -0
  181. data/lib/apiwork/representation/inheritance.rb +78 -0
  182. data/lib/apiwork/representation/model_detector.rb +75 -0
  183. data/lib/apiwork/representation/root_key.rb +35 -0
  184. data/lib/apiwork/representation/serializer.rb +127 -0
  185. data/lib/apiwork/request.rb +79 -0
  186. data/lib/apiwork/response.rb +56 -0
  187. data/lib/apiwork/union.rb +102 -0
  188. data/lib/apiwork/version.rb +2 -2
  189. data/lib/apiwork.rb +61 -3
  190. data/lib/generators/apiwork/api_generator.rb +38 -0
  191. data/lib/generators/apiwork/contract_generator.rb +25 -0
  192. data/lib/generators/apiwork/install_generator.rb +27 -0
  193. data/lib/generators/apiwork/representation_generator.rb +25 -0
  194. data/lib/generators/apiwork/templates/api/api.rb.tt +4 -0
  195. data/lib/generators/apiwork/templates/contract/contract.rb.tt +6 -0
  196. data/lib/generators/apiwork/templates/install/application_contract.rb.tt +5 -0
  197. data/lib/generators/apiwork/templates/install/application_representation.rb.tt +5 -0
  198. data/lib/generators/apiwork/templates/representation/representation.rb.tt +6 -0
  199. data/lib/tasks/apiwork.rake +102 -0
  200. metadata +319 -19
  201. data/.rubocop.yml +0 -8
  202. data/sig/apiwork.rbs +0 -4
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Introspection
5
+ module Param
6
+ # @api public
7
+ # UUID param representing universally unique identifier values.
8
+ #
9
+ # @example Basic usage
10
+ # param.type # => :uuid
11
+ # param.scalar? # => true
12
+ # param.uuid? # => true
13
+ #
14
+ # @example Capabilities
15
+ # param.formattable? # => false
16
+ #
17
+ # @example Enum
18
+ # if param.enum?
19
+ # param.enum # => ["550e8400-e29b-41d4-a716-446655440000"]
20
+ # param.enum_reference? # => false
21
+ # end
22
+ class UUID < Base
23
+ # @api public
24
+ # Whether this param is scalar.
25
+ #
26
+ # @return [Boolean]
27
+ def scalar?
28
+ true
29
+ end
30
+
31
+ # @api public
32
+ # Whether this param has an enum.
33
+ #
34
+ # @return [Boolean]
35
+ def enum?
36
+ @dump[:enum].present?
37
+ end
38
+
39
+ # @api public
40
+ # The enum for this param.
41
+ #
42
+ # @return [Array<String>, Symbol, nil]
43
+ def enum
44
+ @dump[:enum]
45
+ end
46
+
47
+ # @api public
48
+ # Whether this param is an enum reference.
49
+ #
50
+ # @return [Boolean]
51
+ def enum_reference?
52
+ @dump[:enum].is_a?(Symbol)
53
+ end
54
+
55
+ # @api public
56
+ # Whether this param is a UUID.
57
+ #
58
+ # @return [Boolean]
59
+ def uuid?
60
+ true
61
+ end
62
+
63
+ # @api public
64
+ # Whether this param is formattable.
65
+ #
66
+ # @return [Boolean]
67
+ def formattable?
68
+ false
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Introspection
5
+ module Param
6
+ class << self
7
+ def build(dump)
8
+ case dump[:type]
9
+ when :string then String.new(dump)
10
+ when :integer then Integer.new(dump)
11
+ when :number then Number.new(dump)
12
+ when :decimal then Decimal.new(dump)
13
+ when :boolean then Boolean.new(dump)
14
+ when :datetime then DateTime.new(dump)
15
+ when :date then Date.new(dump)
16
+ when :time then Time.new(dump)
17
+ when :uuid then UUID.new(dump)
18
+ when :binary then Binary.new(dump)
19
+ when :unknown then Unknown.new(dump)
20
+ when :array then Array.new(dump)
21
+ when :object then Object.new(dump)
22
+ when :union then Union.new(dump)
23
+ when :literal then Literal.new(dump)
24
+ when :reference then Reference.new(dump)
25
+ else Unknown.new(dump)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Introspection
5
+ # @api public
6
+ # Wraps custom type definitions.
7
+ #
8
+ # Types can be objects (with shapes) or unions (with variants).
9
+ #
10
+ # @example Object type
11
+ # api.types[:address].object? # => true
12
+ # api.types[:address].shape[:city] # => Param for city field
13
+ #
14
+ # @example Union type
15
+ # api.types[:payment_method].union? # => true
16
+ # api.types[:payment_method].variants # => [Param, ...]
17
+ # api.types[:payment_method].discriminator # => :type
18
+ class Type
19
+ def initialize(dump)
20
+ @dump = dump
21
+ end
22
+
23
+ # @api public
24
+ # The type for this type.
25
+ #
26
+ # @return [Symbol, nil]
27
+ def type
28
+ @dump[:type]
29
+ end
30
+
31
+ # @api public
32
+ # Whether this type is an object.
33
+ #
34
+ # @return [Boolean]
35
+ def object?
36
+ type == :object
37
+ end
38
+
39
+ # @api public
40
+ # Whether this type is a union.
41
+ #
42
+ # @return [Boolean]
43
+ def union?
44
+ type == :union
45
+ end
46
+
47
+ # @api public
48
+ # The shape for this type.
49
+ #
50
+ # @return [Hash{Symbol => Param}]
51
+ def shape
52
+ @shape ||= @dump[:shape].transform_values { |dump| Param.build(dump) }
53
+ end
54
+
55
+ # @api public
56
+ # The variants for this type.
57
+ #
58
+ # @return [Array<Param>]
59
+ def variants
60
+ @variants ||= @dump[:variants].map { |variant| Param.build(variant) }
61
+ end
62
+
63
+ # @api public
64
+ # The discriminator for this type.
65
+ #
66
+ # @return [Symbol, nil]
67
+ def discriminator
68
+ @dump[:discriminator]
69
+ end
70
+
71
+ # @api public
72
+ # The description for this type.
73
+ #
74
+ # @return [String, nil]
75
+ def description
76
+ @dump[:description]
77
+ end
78
+
79
+ # @api public
80
+ # The extends for this type.
81
+ #
82
+ # @return [Array<Symbol>]
83
+ def extends
84
+ @dump[:extends]
85
+ end
86
+
87
+ # @api public
88
+ # Whether this type extends other types.
89
+ #
90
+ # @return [Boolean]
91
+ def extends?
92
+ extends.any?
93
+ end
94
+
95
+ # @api public
96
+ # The example for this type.
97
+ #
98
+ # @return [Object, nil]
99
+ def example
100
+ @dump[:example]
101
+ end
102
+
103
+ # @api public
104
+ # Whether this type is deprecated.
105
+ #
106
+ # @return [Boolean]
107
+ def deprecated?
108
+ @dump[:deprecated]
109
+ end
110
+
111
+ # @api public
112
+ # Converts this type to a hash.
113
+ #
114
+ # @return [Hash]
115
+ def to_h
116
+ {
117
+ deprecated: deprecated?,
118
+ description: description,
119
+ discriminator: discriminator,
120
+ example: example,
121
+ extends: extends,
122
+ shape: shape.transform_values(&:to_h),
123
+ type: type,
124
+ variants: variants.map(&:to_h),
125
+ }
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Introspection
5
+ class << self
6
+ def api(api_class, locale: nil)
7
+ with_locale(locale) { API.new(Dump.api(api_class)) }
8
+ end
9
+
10
+ def contract(contract_class, expand: false, locale: nil)
11
+ with_locale(locale) { Contract.new(Dump.contract(contract_class, expand:)) }
12
+ end
13
+
14
+ private
15
+
16
+ def with_locale(locale, &block)
17
+ return yield unless locale
18
+
19
+ unless I18n.available_locales.include?(locale)
20
+ raise ConfigurationError,
21
+ "locale must be one of #{I18n.available_locales.inspect}, got #{locale.inspect}"
22
+ end
23
+
24
+ I18n.with_locale(locale, &block)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ # @api public
5
+ # Represents a validation issue found during request parsing.
6
+ #
7
+ # Issues are returned when request parameters fail validation,
8
+ # coercion, or constraint checks. Access via `contract.issues`.
9
+ class Issue
10
+ # @!attribute [r] code
11
+ # @api public
12
+ # The code for this issue.
13
+ #
14
+ # @return [Symbol]
15
+ # @!attribute [r] detail
16
+ # @api public
17
+ # The detail for this issue.
18
+ #
19
+ # @return [String]
20
+ # @!attribute [r] meta
21
+ # @api public
22
+ # The meta for this issue.
23
+ #
24
+ # @return [Hash]
25
+ # @!attribute [r] path
26
+ # @api public
27
+ # The path for this issue.
28
+ #
29
+ # @return [Array<Symbol, Integer>]
30
+ attr_reader :code,
31
+ :detail,
32
+ :meta,
33
+ :path
34
+
35
+ def initialize(code, detail, meta: {}, path: [])
36
+ @code = code
37
+ @detail = detail
38
+ @path = path.map { |element| element.is_a?(Integer) ? element : element.to_sym }
39
+ @meta = meta
40
+ end
41
+
42
+ # @api public
43
+ # The pointer for this issue.
44
+ #
45
+ # @return [String]
46
+ def pointer
47
+ @pointer ||= JSONPointer.new(*path).to_s
48
+ end
49
+
50
+ # @api public
51
+ # Converts this issue to a hash.
52
+ #
53
+ # @return [Hash]
54
+ def to_h
55
+ {
56
+ code: code,
57
+ detail: detail,
58
+ meta: meta,
59
+ path: path.map(&:to_s),
60
+ pointer: pointer,
61
+ }
62
+ end
63
+
64
+ # @api public
65
+ # Converts this issue to a hash for JSON serialization.
66
+ #
67
+ # @return [Hash]
68
+ def as_json
69
+ to_h
70
+ end
71
+
72
+ # @api public
73
+ # Converts this issue to a string.
74
+ #
75
+ # @return [String]
76
+ def to_s
77
+ "[#{code}]#{path.any? ? " at #{pointer}" : ''} #{detail}"
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ class JSONPointer
5
+ def initialize(*path)
6
+ @path = path
7
+ end
8
+
9
+ def to_s
10
+ return '' if @path.empty?
11
+
12
+ "/#{@path.map { |component| escape(component.to_s) }.join('/')}"
13
+ end
14
+
15
+ private
16
+
17
+ def escape(component)
18
+ component.gsub('~', '~0').gsub('/', '~1')
19
+ end
20
+ end
21
+ end