2727import org .openapitools .codegen .SupportingFile ;
2828import org .openapitools .codegen .model .ModelMap ;
2929import org .openapitools .codegen .model .ModelsMap ;
30+ import org .openapitools .codegen .model .OperationsMap ;
3031import org .openapitools .codegen .utils .ModelUtils ;
3132import org .slf4j .Logger ;
3233import org .slf4j .LoggerFactory ;
@@ -100,9 +101,11 @@ public BetterDartCodegen() {
100101 "Object" ,
101102 "void" ,
102103 "Null" ,
103- "DateTime" ));
104+ "DateTime" ,
105+ "List<int>" ));
104106
105107 reservedWords = loadReservedWords ("/reserved-words/dart.txt" );
108+
106109 }
107110
108111 /** Returns the generator name used to select this codegen via the {@code -g} flag. */
@@ -111,6 +114,16 @@ public String getName() {
111114 return "dart-plus" ;
112115 }
113116
117+ /**
118+ * Escapes a reserved word by appending an underscore suffix
119+ * instead of prepending one. In Dart, names starting with
120+ * underscore are library-private, so we avoid the prefix.
121+ */
122+ @ Override
123+ public String escapeReservedWord (String name ) {
124+ return name + "_" ;
125+ }
126+
114127 /** Returns a short description shown in the help output. */
115128 @ Override
116129 public String getHelp () {
@@ -171,7 +184,7 @@ protected String getFormatterDockerImage() {
171184 /** {@inheritDoc} */
172185 @ Override
173186 protected String [] getFormatterCommands () {
174- return new String [] {"dart format ." };
187+ return new String [] {"dart pub get" , "dart fix --apply" , "dart format ." };
175188 }
176189
177190 /** {@inheritDoc} */
@@ -262,6 +275,9 @@ public void processOpts() {
262275 new SupportingFile ("api_error.mustache" , srcDir , "api_error.dart" ));
263276
264277 final String errorsDir = Path .of (srcDir , "errors" ).toString ();
278+ supportingFiles .add (
279+ new SupportingFile (
280+ "errors/api_error.mustache" , errorsDir , "api_error.dart" ));
265281 supportingFiles .add (
266282 new SupportingFile (
267283 "errors/client_error.mustache" , errorsDir , "client_error.dart" ));
@@ -351,6 +367,11 @@ public void processOpts() {
351367
352368 if (generateTests ) {
353369 supportingFiles .add (new SupportingFile ("test/gitignore" , "" , ".gitignore" ));
370+ supportingFiles .add (
371+ new SupportingFile (
372+ "test/testcontainers_helper.mustache" ,
373+ "test" ,
374+ "testcontainers_helper.dart" ));
354375 supportingFiles .add (
355376 new SupportingFile (
356377 "test/api/pet_api_test.mustache" , "test" , "pet_api_test.dart" ));
@@ -513,10 +534,104 @@ public ModelsMap postProcessModels(ModelsMap objs) {
513534 for (final CodegenProperty prop : model .requiredVars ) {
514535 fixEnumDefaultValue (prop );
515536 }
537+
538+ for (final CodegenProperty prop : model .vars ) {
539+ clearEnumOnPrimitives (prop );
540+ }
541+ for (final CodegenProperty prop : model .allVars ) {
542+ clearEnumOnPrimitives (prop );
543+ }
544+ for (final CodegenProperty prop : model .optionalVars ) {
545+ clearEnumOnPrimitives (prop );
546+ }
547+ for (final CodegenProperty prop : model .requiredVars ) {
548+ clearEnumOnPrimitives (prop );
549+ }
550+
551+ final List <Map <String , String >> dartImports = new ArrayList <>();
552+ for (final String importName : model .imports ) {
553+ if (!languageSpecificPrimitives .contains (importName )
554+ && !typeMapping .containsValue (importName )) {
555+ final Map <String , String > dartImport = new HashMap <>();
556+ dartImport .put ("classname" , importName );
557+ dartImport .put ("filename" , toModelFilename (importName ));
558+ dartImports .add (dartImport );
559+ }
560+ }
561+ modelMap .put ("dartImports" , dartImports );
562+ modelMap .put ("hasDartImports" , !dartImports .isEmpty ());
563+
564+ final Set <String > filteredOneOf = new java .util .LinkedHashSet <>();
565+ for (final String typeName : model .oneOf ) {
566+ if (!languageSpecificPrimitives .contains (typeName )
567+ && !typeName .startsWith ("List<" )
568+ && !typeName .startsWith ("Map<" )
569+ && !typeName .startsWith ("Set<" )) {
570+ filteredOneOf .add (typeName );
571+ }
572+ }
573+ model .oneOf = filteredOneOf ;
574+
575+ final Set <String > filteredAnyOf = new java .util .LinkedHashSet <>();
576+ for (final String typeName : model .anyOf ) {
577+ if (!languageSpecificPrimitives .contains (typeName )
578+ && !typeName .startsWith ("List<" )
579+ && !typeName .startsWith ("Map<" )
580+ && !typeName .startsWith ("Set<" )) {
581+ filteredAnyOf .add (typeName );
582+ }
583+ }
584+ model .anyOf = filteredAnyOf ;
516585 }
517586 return result ;
518587 }
519588
589+ @ SuppressWarnings ("unchecked" )
590+ @ Override
591+ public OperationsMap postProcessOperationsWithModels (
592+ OperationsMap objs , List <ModelMap > allModels ) {
593+ objs = super .postProcessOperationsWithModels (objs , allModels );
594+
595+ final List <Map <String , String >> imports =
596+ (List <Map <String , String >>) objs .get ("imports" );
597+ if (imports != null ) {
598+ imports .removeIf (imp -> {
599+ final String importName = imp .getOrDefault ("import" , "" );
600+ final String className =
601+ importName .contains ("." )
602+ ? importName .substring (importName .lastIndexOf ('.' ) + 1 )
603+ : importName ;
604+ return languageSpecificPrimitives .contains (className )
605+ || typeMapping .containsValue (className )
606+ || className .startsWith ("List<" )
607+ || className .equals ("List" )
608+ || className .startsWith ("Map<" )
609+ || className .equals ("Map" )
610+ || className .startsWith ("Set<" )
611+ || className .equals ("Set" );
612+ });
613+ for (final Map <String , String > imp : imports ) {
614+ if (!imp .containsKey ("className" ) && imp .containsKey ("import" )) {
615+ String className = imp .get ("import" );
616+ if (className .contains ("." )) {
617+ className = className .substring (className .lastIndexOf ('.' ) + 1 );
618+ }
619+ imp .put ("className" , className );
620+ imp .put ("filename" , toModelFilename (className ));
621+ }
622+ }
623+ }
624+ return objs ;
625+ }
626+
627+ private void clearEnumOnPrimitives (CodegenProperty prop ) {
628+ if (prop .isEnum
629+ && (languageSpecificPrimitives .contains (prop .dataType )
630+ || typeMapping .containsValue (prop .dataType ))) {
631+ prop .isEnum = false ;
632+ }
633+ }
634+
520635 private void fixEnumDefaultValue (CodegenProperty prop ) {
521636 if (prop .defaultValue != null && prop .isEnum && prop .defaultValue .contains ("." )) {
522637 final String enumValue = prop .defaultValue .substring (
@@ -696,10 +811,10 @@ private String generateDartAuthClass(
696811 final String url = scheme .getOpenIdConnectUrl ();
697812 return renderDartSchemeAuth (className + "Authenticator" ,
698813 "OpenIdConnectAuthenticator" ,
699- List .of ("oauth/ openid_connect_authenticator.dart" ),
814+ List .of ("openid_connect_authenticator.dart" ),
700815 "required String host, required String clientId, "
701816 + "required String clientSecret, required String redirectUri" ,
702- "host: host, discoveryUrl : '" + url + "', clientId: clientId, "
817+ "host: host, openIdConnectUrl : '" + url + "', clientId: clientId, "
703818 + "clientSecret: clientSecret, redirectUri: redirectUri, "
704819 + "scopes: []" );
705820 }
@@ -716,7 +831,7 @@ private String generateDartOAuthClass(
716831 return renderDartSchemeAuth (
717832 className + "ClientCredentialsAuthenticator" ,
718833 "OAuth2ClientCredentialsAuthenticator" ,
719- List .of ("oauth/ oauth2_client_credentials_authenticator.dart" ),
834+ List .of ("oauth2_client_credentials_authenticator.dart" ),
720835 "required String host, required String clientId, "
721836 + "required String clientSecret" ,
722837 "host: host, clientId: clientId, clientSecret: clientSecret, "
@@ -731,7 +846,7 @@ private String generateDartOAuthClass(
731846 return renderDartSchemeAuth (
732847 className + "PasswordAuthenticator" ,
733848 "OAuth2PasswordAuthenticator" ,
734- List .of ("oauth/ oauth2_password_authenticator.dart" ),
849+ List .of ("oauth2_password_authenticator.dart" ),
735850 "required String host, required String clientId, "
736851 + "required String clientSecret, required String username, "
737852 + "required String password" ,
@@ -749,7 +864,7 @@ private String generateDartOAuthClass(
749864 return renderDartSchemeAuth (
750865 className + "AuthorizationCodeAuthenticator" ,
751866 "OAuth2AuthorizationCodeAuthenticator" ,
752- List .of ("oauth/ oauth2_auth_code_authenticator.dart" ),
867+ List .of ("oauth2_auth_code_authenticator.dart" ),
753868 "required String host, required String clientId, "
754869 + "required String clientSecret, required String redirectUri" ,
755870 "host: host, clientId: clientId, clientSecret: clientSecret, "
@@ -764,7 +879,7 @@ private String generateDartOAuthClass(
764879 return renderDartSchemeAuth (
765880 className + "ImplicitAuthenticator" ,
766881 "OAuth2ImplicitAuthenticator" ,
767- List .of ("oauth/ oauth2_implicit_authenticator.dart" ),
882+ List .of ("oauth2_implicit_authenticator.dart" ),
768883 "required String host, required String clientId" ,
769884 "host: host, clientId: clientId, authorizationUrl: '"
770885 + authUrl + "', scopes: " + scopes );
@@ -852,11 +967,25 @@ protected String generateOptionsFileContent(
852967 params .add (param );
853968 }
854969
970+ final List <Map <String , String >> optionsImports = new ArrayList <>();
971+ for (final CodegenParameter p : optionsParams ) {
972+ if (p .baseType != null
973+ && !languageSpecificPrimitives .contains (p .baseType )
974+ && !typeMapping .containsValue (p .baseType )) {
975+ final Map <String , String > imp = new HashMap <>();
976+ imp .put ("classname" , p .baseType );
977+ imp .put ("filename" , toModelFilename (p .baseType ));
978+ optionsImports .add (imp );
979+ }
980+ }
981+
855982 final Map <String , Object > context = new HashMap <>();
856983 context .put ("className" , className );
857984 context .put ("packageName" , packageName );
858985 context .put ("operationId" , op .operationId );
859986 context .put ("params" , params );
987+ context .put ("dartImports" , optionsImports );
988+ context .put ("hasDartImports" , !optionsImports .isEmpty ());
860989 return renderOptionsTemplate ("api/options.mustache" , context );
861990 }
862991
@@ -868,4 +997,33 @@ protected String getOptionsFilePath(String operationId, String optionsClassName)
868997 getOutputDir (), "lib" , "src" , "api" , "options" , fileName + ".dart" )
869998 .toString ();
870999 }
1000+
1001+ /** Appends options class exports to the barrel file. */
1002+ @ Override
1003+ protected void writeOptionsBarrelFiles (List <Map <String , String >> optionsFiles ) {
1004+ if (optionsFiles .isEmpty ()) {
1005+ return ;
1006+ }
1007+ final Path barrelPath =
1008+ Path .of (getOutputDir (), "lib" , packageName + ".dart" );
1009+ try {
1010+ final StringBuilder sb = new StringBuilder ();
1011+ for (final Map <String , String > meta : optionsFiles ) {
1012+ final String className = meta .get ("optionsClassName" );
1013+ if (className == null ) {
1014+ continue ;
1015+ }
1016+ final String fileName = NamingConvention .SNAKE_CASE .apply (className );
1017+ sb .append ("export 'src/api/options/" )
1018+ .append (fileName )
1019+ .append (".dart';\n " );
1020+ }
1021+ Files .writeString (
1022+ barrelPath ,
1023+ Files .readString (barrelPath ) + sb ,
1024+ StandardCharsets .UTF_8 );
1025+ } catch (IOException e ) {
1026+ LOGGER .warn ("Failed to append options exports to barrel: {}" , e .getMessage ());
1027+ }
1028+ }
8711029}
0 commit comments