Skip to content

Commit d317ed9

Browse files
committed
Refactor spec loading, comment out the dependency on the flutter sdk, clean up duplicated dependency,
1 parent 39a7aa1 commit d317ed9

File tree

8 files changed

+703
-345
lines changed

8 files changed

+703
-345
lines changed

openapi-generator/example/pubspec.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ dev_dependencies:
3636
# flutter_test:
3737
# sdk: flutter
3838
build_runner:
39-
openapi_generator_annotations: ^4.10.0
4039
openapi_generator: ^4.10.0
4140

4241

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import 'dart:async';
2+
import 'dart:convert';
3+
import 'dart:developer';
4+
import 'dart:io';
5+
6+
import 'package:yaml/yaml.dart';
7+
8+
// .json
9+
final jsonRegex = RegExp(r'.*.json$');
10+
// .yml & .yaml
11+
final yamlRegex = RegExp(r'.*(.ya?ml)$');
12+
13+
final _supportedRegexes = [jsonRegex, yamlRegex];
14+
15+
/// Load the provided OpenApiSpec from the disk into an in-memory mapping.
16+
///
17+
/// Throws an error when the file extension doesn't match one of the expected
18+
/// extensions:
19+
/// - json
20+
/// - yaml
21+
/// - yml
22+
///
23+
/// It also throws an error when the specification doesn't exist on disk.
24+
FutureOr<Map<String, dynamic>> loadSpec({required String specPath}) async {
25+
// If the spec file doesn't match any of the currently supported spec formats
26+
// reject the request.
27+
if (!_supportedRegexes.any((fileEnding) => fileEnding.hasMatch(specPath))) {
28+
return Future.error('Invalid spec format');
29+
}
30+
31+
final file = File(specPath);
32+
if (file.existsSync()) {
33+
final contents = await file.readAsString();
34+
late Map<String, dynamic> spec;
35+
if (yamlRegex.hasMatch(specPath)) {
36+
// Load yaml and convert to file
37+
spec = convertYamlMapToDartMap(yamlMap: loadYaml(contents));
38+
} else {
39+
// Convert to json map via json.decode
40+
spec = jsonDecode(contents);
41+
}
42+
43+
return spec;
44+
}
45+
46+
return Future.error('Unable to find spec file $specPath');
47+
}
48+
49+
/// Verify if the [loadedSpec] has a diff compared to the [cachedSpec].
50+
///
51+
/// Returns true when the number of root keys is different.
52+
bool isSpecDirty({
53+
required Map<String, dynamic> cachedSpec,
54+
required Map<String, dynamic> loadedSpec,
55+
}) {
56+
if (loadedSpec.keys.length == cachedSpec.keys.length) {
57+
for (final entry in cachedSpec.entries) {
58+
if (!loadedSpec.containsKey(entry.key)) {
59+
// The original key was removed / renamed in the new map.
60+
// This will likely occur within the paths map.
61+
return true;
62+
}
63+
final lEntry = loadedSpec[entry.key];
64+
// Naive assumption that each of the values are the same
65+
// TODO: Stop assuming the values are of the same type.
66+
if (entry.value is Map) {
67+
final v = entry.value as Map<String, dynamic>;
68+
final l = lEntry as Map<String, dynamic>;
69+
return isSpecDirty(cachedSpec: v, loadedSpec: l);
70+
} else if (entry.value is List) {
71+
// Cast both entries to a list of entries
72+
var v = entry.value as List;
73+
var l = lEntry as List;
74+
75+
if (v.length != l.length) {
76+
return true;
77+
}
78+
79+
try {
80+
// Cast the list into it's typed variants
81+
if (v.every((element) => element is num)) {
82+
if (v.every((element) => element is int)) {
83+
v = v.cast<int>();
84+
l = l.cast<int>();
85+
} else if (v.every((element) => element is double)) {
86+
v = v.cast<double>();
87+
l = l.cast<double>();
88+
}
89+
} else if (v.every((element) => element is String)) {
90+
v = v.cast<String>();
91+
l = l.cast<String>();
92+
} else if (v.every((element) => element is bool)) {
93+
v = v.cast<bool>();
94+
l = l.cast<bool>();
95+
}
96+
} on TypeError catch (e, st) {
97+
log('Failed to cast entry, this may be due to an API change',
98+
stackTrace: st, error: e);
99+
// If there is an error casting this is likely due to the type of L not
100+
// matching which could indicate that the type of the loaded spec may
101+
// have changed.
102+
return true;
103+
}
104+
105+
// Loop through each of the entries, this now means ordering matters.
106+
// TODO: Verify if this is desired behaviour.
107+
for (var i = 0; i < v.length; i++) {
108+
if (v[i] != l[i]) {
109+
return true;
110+
}
111+
}
112+
113+
return false;
114+
} else {
115+
try {
116+
// The value is a scalar value
117+
var v = entry.value;
118+
var l = lEntry;
119+
120+
if (v is num) {
121+
if (v is int) {
122+
return v != (l as int);
123+
} else {
124+
return v != (l as double);
125+
}
126+
} else if (v is bool) {
127+
return v != (l as bool);
128+
} else if (v is String) {
129+
return v != (l as String);
130+
} else {
131+
// Enums are represented as lists
132+
return false;
133+
}
134+
} catch (e, st) {
135+
// TODO: This is likely a poor assumption to make
136+
log('Failed to parse value, likely do to type change',
137+
stackTrace: st, error: e);
138+
return true;
139+
}
140+
}
141+
}
142+
return false;
143+
}
144+
return true;
145+
}
146+
147+
/// Convert the [YamlMap] to a Dart [Map].
148+
///
149+
/// Converts a [YamlMap] and it's children into a [Map]. This involes expanding
150+
/// [YamlList] & [YamlMap] nodes into their entries. [YamlScalar] values are
151+
/// directly set.
152+
///
153+
/// Currently this makes the assumption that [YamlList] node shouldn't be a valid
154+
/// child entry within another [YamlList] and ignores the contents.
155+
Map<String, dynamic> convertYamlMapToDartMap({required YamlMap yamlMap}) {
156+
final transformed = <String, dynamic>{};
157+
158+
yamlMap.forEach((key, value) {
159+
late dynamic content;
160+
if (value is YamlList) {
161+
// Parse list entries
162+
content = [];
163+
value.forEach((element) {
164+
if (element is YamlList) {
165+
// TODO: Is this a potential case.
166+
log('Found a YamlList within a YamlList');
167+
} else if (element is YamlMap) {
168+
content.add(convertYamlMapToDartMap(yamlMap: element));
169+
} else {
170+
content.add(element);
171+
}
172+
});
173+
} else if (value is YamlMap) {
174+
// Parse the sub mapyamlParsedMap
175+
content = convertYamlMapToDartMap(
176+
yamlMap: YamlMap.internal(value.nodes, value.span, value.style));
177+
} else if (value is YamlScalar) {
178+
// Pull the value out of the scalar
179+
content = value.value;
180+
} else {
181+
// Value is a supported dart type
182+
content = value;
183+
}
184+
transformed['$key'] = content;
185+
});
186+
187+
return transformed;
188+
}
189+
190+
Future<void> cacheSpec({
191+
required String outputDirectory,
192+
required Map<String, dynamic> spec,
193+
}) async {}

openapi-generator/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies:
1313
openapi_generator_annotations: ^4.11.0
1414
analyzer: '>=2.0.0 <=6.0.0'
1515
openapi_generator_cli: ^4.11.0
16+
yaml: ^3.1.2
1617

1718
dev_dependencies:
1819
test: ^1.24.2

0 commit comments

Comments
 (0)