77
88import java .util .*;
99
10+ import com .google .common .base .Splitter ;
11+ import com .google .common .collect .*;
12+ import edu .umd .cs .findbugs .annotations .NonNull ;
1013import io .jooby .internal .openapi .OperationExt ;
1114import io .pebbletemplates .pebble .error .PebbleException ;
1215import io .pebbletemplates .pebble .extension .Filter ;
1619
1720public enum Filters implements Filter {
1821 curl {
22+ private static final CharSequence Accept = new HeaderName ("Accept" );
23+ private static final CharSequence ContentType = new HeaderName ("Content-Type" );
24+
1925 @ Override
2026 public Object apply (
2127 Object input ,
@@ -30,80 +36,143 @@ public Object apply(
3036 "Argument must be " + Operation .class .getName () + ". Got: " + input );
3137 }
3238 var snippetResolver = (SnippetResolver ) context .getVariable ("snippetResolver" );
33-
34- var options = new LinkedHashMap <String , Set <String >>();
35- options .put ("-X" , Set .of (null ));
36- operation
37- .getProduces ()
38- .forEach (
39- produces -> {
40- options
41- .computeIfAbsent ("-H" , (key ) -> new LinkedHashSet <>())
42- .add ("'Accept: " + produces + "'" );
43- });
44-
45- // Convert to map so can override any generated option
46- var optionList = new ArrayList <>(args .values ());
47- for (int i = 0 ; i < optionList .size (); ) {
48- var key = optionList .get (i ).toString ();
49- String value = null ;
50- if (i + 1 < optionList .size ()) {
51- var next = optionList .get (i + 1 );
52- if (next .toString ().startsWith ("-" )) {
53- i += 1 ;
54- } else {
55- value = next .toString ();
56- i += 2 ;
57- }
58- } else {
59- i += 1 ;
60- }
61- var values = options .computeIfAbsent (key , k -> new LinkedHashSet <>());
62- if (value != null ) {
63- values .add (value );
64- }
39+ var options = args (args );
40+ var method =
41+ Optional .of (options .removeAll ("-X" ))
42+ .map (Collection ::iterator )
43+ .filter (Iterator ::hasNext )
44+ .map (Iterator ::next )
45+ .orElse (operation .getMethod ())
46+ .toUpperCase ();
47+ /* Accept/Content-Type: */
48+ var addAccept = true ;
49+ var addContentType = true ;
50+ if (options .containsKey ("-H" )) {
51+ var headers = parseHeaders (options .get ("-H" ));
52+ addAccept = !headers .containsKey (Accept );
53+ addContentType = !headers .containsKey (ContentType );
54+ }
55+ if (addAccept ) {
56+ operation .getProduces ().forEach (value -> options .put ("-H" , "'Accept: " + value + "'" ));
57+ }
58+ if (addContentType && !READ_METHODS .contains (method )) {
59+ operation
60+ .getConsumes ()
61+ .forEach (value -> options .put ("-H" , "'Content-Type: " + value + "'" ));
62+ }
63+ /* Method */
64+ if (!options .containsKey ("-X" )) {
65+ options .put ("-X" , method );
6566 }
6667 return snippetResolver .apply (
67- "curl" , Map .of ("url" , operation .getPattern (), "options" , toString (options )));
68+ name () , Map .of ("url" , operation .getPattern (), "options" , toString (options )));
6869 } catch (PebbleException pebbleException ) {
6970 throw pebbleException ;
7071 } catch (Exception exception ) {
7172 throw new PebbleException (exception , name () + " failed to generate output" );
7273 }
7374 }
7475
75- private String toString (Map <String , Set <String >> options ) {
76- if (options .isEmpty ()) {
77- return "" ;
78- }
79- var sb = new StringBuilder ();
80- var separator = " " ;
81- for (var e : options .entrySet ()) {
82- var values = e .getValue ();
83- if (values .isEmpty ()) {
84- sb .append (e .getKey ()).append (separator );
85- } else {
86- for (var value : e .getValue ()) {
87- sb .append (e .getKey ()).append (separator );
88- sb .append (value ).append (separator );
89- }
90- }
91- }
92- sb .deleteCharAt (sb .length () - separator .length ());
93- return sb .toString ();
94- }
95-
9676 @ Override
9777 public List <String > getArgumentNames () {
9878 return null ;
9979 }
10080 };
10181
82+ protected Multimap <CharSequence , String > parseHeaders (Collection <String > headers ) {
83+ Multimap <CharSequence , String > result = LinkedHashMultimap .create ();
84+ for (var line : headers ) {
85+ if (line .startsWith ("'" ) && line .endsWith ("'" )) {
86+ line = line .substring (1 , line .length () - 1 );
87+ }
88+ var header = Splitter .on (':' ).trimResults ().omitEmptyStrings ().splitToList (line );
89+ if (header .size () != 2 ) {
90+ throw new IllegalArgumentException ("Invalid header: " + line );
91+ }
92+ result .put (new HeaderName (header .get (0 )), header .get (1 ));
93+ }
94+ return result ;
95+ }
96+
97+ protected static final Set <String > READ_METHODS = Set .of ("GET" , "HEAD" );
98+
99+ protected String toString (Multimap <String , String > options ) {
100+ if (options .isEmpty ()) {
101+ return "" ;
102+ }
103+ var sb = new StringBuilder ();
104+ var separator = " " ;
105+ options .forEach (
106+ (k , v ) -> {
107+ sb .append (k ).append (separator );
108+ if (v != null && !v .isEmpty ()) {
109+ sb .append (v ).append (separator );
110+ }
111+ });
112+ sb .deleteCharAt (sb .length () - separator .length ());
113+ return sb .toString ();
114+ }
115+
116+ protected Multimap <String , String > args (Map <String , Object > args ) {
117+ Multimap <String , String > result = LinkedHashMultimap .create ();
118+ var optionList = new ArrayList <>(args .values ());
119+ for (int i = 0 ; i < optionList .size (); ) {
120+ var key = optionList .get (i ).toString ();
121+ String value = null ;
122+ if (i + 1 < optionList .size ()) {
123+ var next = optionList .get (i + 1 );
124+ if (next .toString ().startsWith ("-" )) {
125+ i += 1 ;
126+ } else {
127+ value = next .toString ();
128+ i += 2 ;
129+ }
130+ } else {
131+ i += 1 ;
132+ }
133+ result .put (key , value == null ? "" : value );
134+ }
135+ return result ;
136+ }
137+
102138 public static Map <String , Filter > fn () {
103139 Map <String , Filter > functions = new HashMap <>();
104140 for (var value : values ()) {
105141 functions .put (value .name (), value );
106142 }
107143 return functions ;
108144 }
145+
146+ protected record HeaderName (String value ) implements CharSequence {
147+
148+ @ Override
149+ public int length () {
150+ return value .length ();
151+ }
152+
153+ @ Override
154+ public boolean equals (Object obj ) {
155+ return value .equalsIgnoreCase (obj .toString ());
156+ }
157+
158+ @ Override
159+ public int hashCode () {
160+ return value .toLowerCase ().hashCode ();
161+ }
162+
163+ @ Override
164+ public char charAt (int index ) {
165+ return value .charAt (index );
166+ }
167+
168+ @ NonNull @ Override
169+ public CharSequence subSequence (int start , int end ) {
170+ return value .subSequence (start , end );
171+ }
172+
173+ @ Override
174+ public String toString () {
175+ return value ;
176+ }
177+ }
109178}
0 commit comments