Skip to content

Commit 1990575

Browse files
committed
Share behaviour between schema objects
This sets up the ability for shared code between Schema objects and providing customisation for ones of different OpenAPI versions. It marks a direction towards modelling the specification of JSON Schema 2020-12 JSON Schema Validation [1] specifically rather trying to understand how different dialects could be used. My expectation is that it is very much an edge case that someone would use another dialect and it seems incredibly hard to consider how they can be supported. There's likely some optimising that could be done of the shared example specs, but I wanted to get something together on the faster side. This also has a rename of Schema::OasDialect3_1 to Schema::V3_1. I don't think we'll ever try to model other dialects so I think it's best to have a single class to try model the schemas for 3.1 and hopefully later versions. [1]: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01 WIP
1 parent 9d3e063 commit 1990575

File tree

16 files changed

+689
-619
lines changed

16 files changed

+689
-619
lines changed

json-schema-for-3.1.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Things have got complex with schemas in OpenAPI 3.1
66

77
How things might work:
88

9-
- when a schema factory is created, it determines whether the dialect is suported
9+
- when a schema factory is created, it determines whether the dialect is supported
1010
- it then creates a factory based on the dialect
1111
- if there is a reference in it this is resolved
1212
- there could be complexities in the resolving process because of the id field - does it become relative to this?
@@ -90,3 +90,17 @@ additionalProperties: single json schema
9090

9191
unevaluatedItems - single schema
9292
unevaluatedProperties: single schema
93+
94+
95+
## Returning to this in 2025
96+
97+
Assumption: it'll be extremely rare for usage of the advanced schema fields like dynamicRefs and dynamicAnchors, let's see what we can implement that meets most use cases and hopefully doesn't crash on complex ones
98+
99+
Current idea is create a Schema::Common which can share methods between both schema objects that are shared, then add distinctions for differences
100+
101+
At point of shutting down on 10th January 2025 I was wondering about how schemas merge. I also decided to defer thinking about referenceable node object factory.
102+
103+
I learnt that merging seems largely undefined in JSON Schema, as far as I can tell and I'm just going with a strategy of most recent field wins.
104+
105+
I've set up a Node::Schema class for common schema methods and Node::Schema::v3_0 and v3_1Up classes for specific changes. Need to flesh out
106+
tests and then behaviour that differs between them

lib/openapi3_parser/node/schema.rb

Lines changed: 243 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,250 @@
11
# frozen_string_literal: true
22

3+
require "openapi3_parser/node/object"
4+
35
module Openapi3Parser
46
module Node
5-
module Schema
7+
# Base class for common behaviour between Schema objects of different
8+
# OpenAPI versions. It is expected to be treated as an abstract class
9+
#
10+
# rubocop:disable Metrics/ClassLength
11+
class Schema < Node::Object
12+
# This is used to provide a name for the schema based on it's position in
13+
# an OpenAPI document.
14+
#
15+
# For example it's common to have an OpenAPI document structured like so:
16+
# components:
17+
# schemas:
18+
# Product:
19+
# properties:
20+
# product_id:
21+
# type: string
22+
# description:
23+
# type: string
24+
#
25+
# and there is then implied meaning in the field name of Product, ie
26+
# that schema now represents a product. This data is not easily or
27+
# consistently made available as it is part of the path to the data
28+
# rather than the data itself. Instead the field that would be more
29+
# appropriate would be "title" within a schema.
30+
#
31+
# As this is a common pattern in OpenAPI docs this provides a method
32+
# to look up this contextual name of the schema so it can be referenced
33+
# when working with the document, it only considers a field to be
34+
# name if it is within a group called schemas (as is the case
35+
# in #/components/schemas)
36+
#
37+
# @return [String, nil]
38+
def name
39+
segments = node_context.source_locations.first.pointer.segments
40+
segments[-1] if segments[-2] == "schemas"
41+
end
42+
43+
# @return [String, nil]
44+
def title
45+
self["title"]
46+
end
47+
48+
# @return [Numeric, nil]
49+
def multiple_of
50+
self["multipleOf"]
51+
end
52+
53+
# @return [Integer, nil]
54+
def maximum
55+
self["maximum"]
56+
end
57+
58+
# @return [Boolean]
59+
def exclusive_maximum?
60+
self["exclusiveMaximum"]
61+
end
62+
63+
# @return [Integer, nil]
64+
def minimum
65+
self["minimum"]
66+
end
67+
68+
# @return [Boolean]
69+
def exclusive_minimum?
70+
self["exclusiveMinimum"]
71+
end
72+
73+
# @return [Integer, nil]
74+
def max_length
75+
self["maxLength"]
76+
end
77+
78+
# @return [Integer]
79+
def min_length
80+
self["minLength"]
81+
end
82+
83+
# @return [String, nil]
84+
def pattern
85+
self["pattern"]
86+
end
87+
88+
# @return [Integer, nil]
89+
def max_items
90+
self["maxItems"]
91+
end
92+
93+
# @return [Integer]
94+
def min_items
95+
self["minItems"]
96+
end
97+
98+
# @return [Boolean]
99+
def unique_items?
100+
self["uniqueItems"]
101+
end
102+
103+
# @return [Integer, nil]
104+
def max_properties
105+
self["maxProperties"]
106+
end
107+
108+
# @return [Integer]
109+
def min_properties
110+
self["minProperties"]
111+
end
112+
113+
# @return [Node::Array<String>, nil]
114+
def required
115+
self["required"]
116+
end
117+
118+
# Returns whether a property is a required field or not. Can accept the
119+
# property name or a schema
120+
#
121+
# @param [String, Schema] property
122+
# @return [Boolean]
123+
def requires?(property)
124+
if property.is_a?(self.class)
125+
# compare node_context of objects to ensure references aren't treated
126+
# as equal - only direct properties of this object will pass.
127+
properties.to_h
128+
.select { |k, _| required.to_a.include?(k) }
129+
.any? { |_, schema| schema.node_context == property.node_context }
130+
else
131+
required.to_a.include?(property)
132+
end
133+
end
134+
135+
# @return [Node::Array<Object>, nil]
136+
def enum
137+
self["enum"]
138+
end
139+
140+
# @return [String, nil]
141+
def type
142+
self["type"]
143+
end
144+
145+
# @return [Node::Array<Schema>, nil]
146+
def all_of
147+
self["allOf"]
148+
end
149+
150+
# @return [Node::Array<Schema>, nil]
151+
def one_of
152+
self["oneOf"]
153+
end
154+
155+
# @return [Node::Array<Schema>, nil]
156+
def any_of
157+
self["anyOf"]
158+
end
159+
160+
# @return [Schema, nil]
161+
def not
162+
self["not"]
163+
end
164+
165+
# @return [Schema, nil]
166+
def items
167+
self["items"]
168+
end
169+
170+
# @return [Map<String, Schema>]
171+
def properties
172+
self["properties"]
173+
end
174+
175+
# @return [Boolean]
176+
def additional_properties?
177+
self["additionalProperties"] != false
178+
end
179+
180+
# @return [Schema, nil]
181+
def additional_properties_schema
182+
properties = self["additionalProperties"]
183+
return if [true, false].include?(properties)
184+
185+
properties
186+
end
187+
188+
# @return [String, nil]
189+
def description
190+
self["description"]
191+
end
192+
193+
# @return [String, nil]
194+
def description_html
195+
render_markdown(description)
196+
end
197+
198+
# @return [String, nil]
199+
def format
200+
self["format"]
201+
end
202+
203+
# @return [Any]
204+
def default
205+
self["default"]
206+
end
207+
208+
# @return [Boolean]
209+
def nullable?
210+
self["nullable"]
211+
end
212+
213+
# @return [Discriminator, nil]
214+
def discriminator
215+
self["discriminator"]
216+
end
217+
218+
# @return [Boolean]
219+
def read_only?
220+
self["readOnly"]
221+
end
222+
223+
# @return [Boolean]
224+
def write_only?
225+
self["writeOnly"]
226+
end
227+
228+
# @return [Xml, nil]
229+
def xml
230+
self["xml"]
231+
end
232+
233+
# @return [ExternalDocumentation, nil]
234+
def external_docs
235+
self["externalDocs"]
236+
end
237+
238+
# @return [Any]
239+
def example
240+
self["example"]
241+
end
242+
243+
# @return [Boolean]
244+
def deprecated?
245+
self["deprecated"]
246+
end
6247
end
248+
# rubocop:enable Metrics/ClassLength
7249
end
8250
end

lib/openapi3_parser/node/schema/oas_dialect3_1.rb

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)