@@ -5,35 +5,83 @@ import 'package:space_gen/src/logger.dart';
55import 'package:space_gen/src/spec.dart' ;
66import 'package:space_gen/src/string.dart' ;
77
8+ T _required <T >(ParseContext context, Json json, String key) {
9+ final value = json[key];
10+ if (value == null ) {
11+ throw FormatException (
12+ 'Required key not found: $key in ${context .pointer }: $json ' ,
13+ );
14+ }
15+ return value as T ;
16+ }
17+
18+ void _expect (bool condition, ParseContext context, Json json, String message) {
19+ if (! condition) {
20+ throw FormatException ('$message in ${context .pointer }' );
21+ }
22+ }
23+
24+ T ? _optional <T >(ParseContext context, Json json, String key) {
25+ final value = json[key];
26+ if (value is T ? ) {
27+ return value;
28+ }
29+ throw FormatException (
30+ 'Key $key is not of type $T : $value (in ${context .pointer })' ,
31+ );
32+ }
33+
34+ // void _unimplemented(Json json, String key) {
35+ // final value = json[key];
36+ // if (value != null) {
37+ // throw UnimplementedError('Unsupported key: $key in $json');
38+ // }
39+ // }
40+
41+ void _ignored (ParseContext context, Json json, String key) {
42+ final value = json[key];
43+ if (value != null ) {
44+ logger.detail ('Ignoring key: $key in ${context .pointer }' );
45+ }
46+ }
47+
48+ void _warn (ParseContext context, Json json, String message) {
49+ logger.warn ('$message in ${context .pointer }' );
50+ }
51+
52+ void _error (ParseContext context, Json json, String message) {
53+ throw FormatException ('$message in ${context .pointer }' );
54+ }
55+
856/// Parse a parameter from a json object.
957Parameter parseParameter ({required Json json, required ParseContext context}) {
10- final schema = _optional <Json >(json, 'schema' );
58+ final schema = _optional <Json >(context, json, 'schema' );
1159 final hasSchema = schema != null ;
12- final hasContent = _optional <Json >(json, 'content' ) != null ;
60+ final hasContent = _optional <Json >(context, json, 'content' ) != null ;
1361
1462 // Common fields.
15- final name = _required <String >(json, 'name' );
16- final description = _optional <String >(json, 'description' );
17- final required = _optional <bool >(json, 'required' ) ?? false ;
18- final sendIn = SendIn .fromJson (_required <String >(json, 'in' ));
19- _ignored (json, 'deprecated' );
20- _ignored (json, 'allowEmptyValue' );
63+ final name = _required <String >(context, json, 'name' );
64+ final description = _optional <String >(context, json, 'description' );
65+ final required = _optional <bool >(context, json, 'required' ) ?? false ;
66+ final sendIn = SendIn .fromJson (_required <String >(context, json, 'in' ));
67+ _ignored (context, json, 'deprecated' );
68+ _ignored (context, json, 'allowEmptyValue' );
2169
2270 final SchemaRef type;
2371 if (hasSchema) {
2472 if (hasContent) {
25- _error (json, 'Parameter cannot have both schema and content' );
73+ _error (context, json, 'Parameter cannot have both schema and content' );
2674 }
2775 // Schema fields.
2876 type = parseSchemaOrRef (json: schema, context: context.key ('schema' ));
29- _ignored (json, 'style' );
30- _ignored (json, 'explode' );
31- _ignored (json, 'allowReserved' );
32- _ignored (json, 'example' );
33- _ignored (json, 'examples' );
77+ _ignored (context, json, 'style' );
78+ _ignored (context, json, 'explode' );
79+ _ignored (context, json, 'allowReserved' );
80+ _ignored (context, json, 'example' );
81+ _ignored (context, json, 'examples' );
3482 } else {
3583 if (! hasSchema && ! hasContent) {
36- _error (json, 'Parameter must have either schema or content' );
84+ _error (context, json, 'Parameter must have either schema or content' );
3785 }
3886 // Content fields.
3987 // Use an explicit throw so Dart can see `type` is always set.
@@ -78,16 +126,16 @@ Schema parseSchema(Json json, ParseContext context) {
78126 );
79127 }
80128
81- _ignored (json, 'nullable' );
82- _ignored (json, 'readOnly' );
83- _ignored (json, 'writeOnly' );
84- _ignored (json, 'discriminator' );
85- _ignored (json, 'xml' );
86- _ignored (json, 'example' );
87- _ignored (json, 'examples' );
88- _ignored (json, 'externalDocs' );
129+ _ignored (context, json, 'nullable' );
130+ _ignored (context, json, 'readOnly' );
131+ _ignored (context, json, 'writeOnly' );
132+ _ignored (context, json, 'discriminator' );
133+ _ignored (context, json, 'xml' );
134+ _ignored (context, json, 'example' );
135+ _ignored (context, json, 'examples' );
136+ _ignored (context, json, 'externalDocs' );
89137
90- final defaultValue = _optional <dynamic >(json, 'default' );
138+ final defaultValue = _optional <dynamic >(context, json, 'default' );
91139
92140 final required = json['required' ] as List <dynamic >? ?? [];
93141 final description = json['description' ] as String ? ?? '' ;
@@ -238,13 +286,13 @@ Map<String, SchemaRef> parseProperties({
238286}
239287
240288RequestBody parseRequestBody (Json requestBodyJson, ParseContext context) {
241- final content = _required <Json >(requestBodyJson, 'content' );
242- final applicationJson = _required <Json >(content, 'application/json' );
289+ final content = _required <Json >(context, requestBodyJson, 'content' );
290+ final applicationJson = _required <Json >(context, content, 'application/json' );
243291 final schema = parseSchemaOrRef (
244- json: _required <Json >(applicationJson, 'schema' ),
292+ json: _required <Json >(context, applicationJson, 'schema' ),
245293 context: context.addSnakeName ('request' ).key ('requestBody' ),
246294 );
247- _ignored (requestBodyJson, 'description' );
295+ _ignored (context, requestBodyJson, 'description' );
248296
249297 final isRequired = requestBodyJson['required' ] as bool ? ?? false ;
250298 final body = RequestBody (
@@ -270,12 +318,13 @@ Endpoint parseEndpoint({
270318 final context = parentContext.addSnakeName (snakeName);
271319
272320 final responses = parseResponses (
273- _optional <Json >(json, 'responses' ),
321+ _optional <Json >(context, json, 'responses' ),
274322 context.key ('responses' ),
275323 );
276- final tags = _optional <List <dynamic >>(json, 'tags' );
324+ final tags = _optional <List <dynamic >>(context, json, 'tags' );
277325 final tag = tags? .firstOrNull as String ? ?? 'Default' ;
278- final parametersJson = _optional <List <dynamic >>(json, 'parameters' ) ?? [];
326+ final parametersJson =
327+ _optional <List <dynamic >>(context, json, 'parameters' ) ?? [];
279328 final parameters = parametersJson
280329 .cast <Json >()
281330 .indexed
@@ -407,17 +456,48 @@ Components parseComponents(Json? json, ParseContext context) {
407456 return Components (schemas: schemas, requestBodies: requestBodies);
408457}
409458
410- Spec parseSpec (Json json, ParseContext context) {
411- final servers = _required <List <dynamic >>(json, 'servers' );
459+ Info parseInfo (Json json, ParseContext context) {
460+ final title = _required <String >(context, json, 'title' );
461+ final version = _required <String >(context, json, 'version' );
462+ _ignored (context, json, 'summary' );
463+ _ignored (context, json, 'description' );
464+ _ignored (context, json, 'termsOfService' );
465+ _ignored (context, json, 'contact' );
466+ _ignored (context, json, 'license' );
467+ return Info (title, version);
468+ }
469+
470+ OpenApi parseOpenApi (Json json, ParseContext context) {
471+ final minimumVersion = Version .parse ('3.1.0' );
472+ final versionString = _required <String >(context, json, 'openapi' );
473+ final version = Version .parse (versionString);
474+ if (version < minimumVersion) {
475+ _warn (
476+ context,
477+ json,
478+ '$version may not be supported, only tested with 3.1.0' ,
479+ );
480+ }
481+
482+ final infoJson = _required <Json >(context, json, 'info' );
483+ final info = parseInfo (infoJson, context.key ('info' ));
484+
485+ final servers = _required <List <dynamic >>(context, json, 'servers' );
412486 final firstServer = servers.first as Json ;
413- final serverUrl = _required <String >(firstServer, 'url' );
487+ final serverUrl = _required <String >(context, firstServer, 'url' );
414488
415- final paths = _required <Json >(json, 'paths' );
489+ final paths = _required <Json >(context, json, 'paths' );
416490 final endpoints = < Endpoint > [];
417491 for (final pathEntry in paths.entries) {
418492 final path = pathEntry.key;
419- _expect (path.isNotEmpty, json, 'Path cannot be empty' );
420- _expect (path.startsWith ('/' ), json, 'Path must start with /: $path ' );
493+ final pathContext = context.key ('paths' ).key (path);
494+ _expect (path.isNotEmpty, pathContext, json, 'Path cannot be empty' );
495+ _expect (
496+ path.startsWith ('/' ),
497+ pathContext,
498+ json,
499+ 'Path must start with /: $path ' ,
500+ );
421501 final pathValue = pathEntry.value as Json ;
422502 for (final method in Method .values) {
423503 final methodValue = pathValue[method.key] as Json ? ;
@@ -426,7 +506,7 @@ Spec parseSpec(Json json, ParseContext context) {
426506 }
427507 endpoints.add (
428508 parseEndpoint (
429- parentContext: context. key ( 'paths' ). key (path) .key (method.key),
509+ parentContext: pathContext .key (method.key),
430510 path: path,
431511 json: methodValue,
432512 method: method,
@@ -438,47 +518,13 @@ Spec parseSpec(Json json, ParseContext context) {
438518 json['components' ] as Json ? ,
439519 context.key ('components' ),
440520 );
441- return Spec (Uri .parse (serverUrl), endpoints, components);
442- }
443-
444- T _required <T >(Json json, String key) {
445- final value = json[key];
446- if (value == null ) {
447- throw FormatException ('Required key not found: $key in $json ' );
448- }
449- return value as T ;
450- }
451-
452- void _expect (bool condition, Json json, String message) {
453- if (! condition) {
454- throw FormatException ('$message in $json ' );
455- }
456- }
457-
458- T ? _optional <T >(Json json, String key) {
459- final value = json[key];
460- if (value is T ? ) {
461- return value;
462- }
463- throw FormatException ('Key $key is not of type $T : $value (from $json )' );
464- }
465-
466- // void _unimplemented(Json json, String key) {
467- // final value = json[key];
468- // if (value != null) {
469- // throw UnimplementedError('Unsupported key: $key in $json');
470- // }
471- // }
472-
473- void _ignored (Json json, String key) {
474- final value = json[key];
475- if (value != null ) {
476- logger.detail ('Ignoring key: $key in $json ' );
477- }
478- }
479-
480- void _error (Json json, String message) {
481- throw FormatException ('$message in $json ' );
521+ return OpenApi (
522+ serverUrl: Uri .parse (serverUrl),
523+ version: version,
524+ info: info,
525+ endpoints: endpoints,
526+ components: components,
527+ );
482528}
483529
484530class RefRegistry {
0 commit comments