2828import io .serverlessworkflow .impl .executors .http .HttpExecutor ;
2929import io .serverlessworkflow .impl .executors .http .HttpExecutor .HttpExecutorBuilder ;
3030import io .serverlessworkflow .impl .resources .ResourceLoaderUtils ;
31+ import io .swagger .v3 .oas .models .media .Schema ;
3132import io .swagger .v3 .oas .models .parameters .Parameter ;
3233import java .util .Collection ;
3334import java .util .HashMap ;
@@ -52,7 +53,10 @@ public void init(CallOpenAPI task, WorkflowDefinition definition) {
5253 OpenAPIArguments with = task .getWith ();
5354 this .processor = new OpenAPIProcessor (with .getOperationId ());
5455 this .resource = with .getDocument ();
55- this .parameters = with .getParameters ().getAdditionalProperties ();
56+ this .parameters =
57+ with .getParameters () != null && with .getParameters ().getAdditionalProperties () != null
58+ ? with .getParameters ().getAdditionalProperties ()
59+ : Map .of ();
5660 this .builder =
5761 HttpExecutor .builder (definition )
5862 .withAuth (with .getAuthentication ())
@@ -62,20 +66,27 @@ public void init(CallOpenAPI task, WorkflowDefinition definition) {
6266 @ Override
6367 public CompletableFuture <WorkflowModel > apply (
6468 WorkflowContext workflowContext , TaskContext taskContext , WorkflowModel input ) {
69+
70+ // In the same workflow, access to an already cached document
71+ final OperationDefinition operationDefinition =
72+ processor .parse (
73+ workflowContext
74+ .definition ()
75+ .resourceLoader ()
76+ .load (
77+ resource ,
78+ ResourceLoaderUtils ::readString ,
79+ workflowContext ,
80+ taskContext ,
81+ input ));
82+
83+ fillHttpBuilder (workflowContext .definition ().application (), operationDefinition );
84+ // One executor per operation, even if the document is the same
85+ // Me may refactor this even further to reuse the same executor (since the base URI is the same,
86+ // but the path differs, although some use cases may require different client configurations for
87+ // different paths...)
6588 Collection <HttpExecutor > executors =
66- workflowContext
67- .definition ()
68- .resourceLoader ()
69- .<Collection <HttpExecutor >>load (
70- resource ,
71- r -> {
72- OperationDefinition o = processor .parse (ResourceLoaderUtils .readString (r ));
73- fillHttpBuilder (workflowContext .definition ().application (), o );
74- return o .getServers ().stream ().map (s -> builder .build (s )).toList ();
75- },
76- workflowContext ,
77- taskContext ,
78- input );
89+ operationDefinition .getServers ().stream ().map (s -> builder .build (s )).toList ();
7990
8091 Iterator <HttpExecutor > iter = executors .iterator ();
8192 if (!iter .hasNext ()) {
@@ -91,9 +102,9 @@ public CompletableFuture<WorkflowModel> apply(
91102 }
92103
93104 private void fillHttpBuilder (WorkflowApplication application , OperationDefinition operation ) {
94- Map <String , Object > headersMap = new HashMap <String , Object >();
95- Map <String , Object > queryMap = new HashMap <String , Object >();
96- Map <String , Object > pathParameters = new HashMap <String , Object >();
105+ Map <String , Object > headersMap = new HashMap <>();
106+ Map <String , Object > queryMap = new HashMap <>();
107+ Map <String , Object > pathParameters = new HashMap <>();
97108
98109 Map <String , Object > bodyParameters = new HashMap <>(parameters );
99110 for (Parameter parameter : operation .getParameters ()) {
@@ -110,6 +121,8 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio
110121 }
111122 }
112123
124+ validateRequiredParameters (operation , headersMap , queryMap , pathParameters );
125+
113126 builder
114127 .withMethod (operation .getMethod ())
115128 .withPath (new OperationPathResolver (operation .getPath (), application , pathParameters ))
@@ -124,4 +137,65 @@ private void param(String name, Map<String, Object> origMap, Map<String, Object>
124137 collectorMap .put (name , value );
125138 }
126139 }
140+
141+ private void validateRequiredParameters (
142+ OperationDefinition operation ,
143+ Map <String , Object > headersMap ,
144+ Map <String , Object > queryMap ,
145+ Map <String , Object > pathParameters ) {
146+
147+ StringBuilder missing = new StringBuilder ();
148+
149+ for (Parameter parameter : operation .getParameters ()) {
150+ if (!Boolean .TRUE .equals (parameter .getRequired ())) {
151+ continue ;
152+ }
153+
154+ String in = parameter .getIn ();
155+ String name = parameter .getName ();
156+
157+ Map <String , Object > targetMap =
158+ switch (in ) {
159+ case "header" -> headersMap ;
160+ case "path" -> pathParameters ;
161+ case "query" -> queryMap ;
162+ default -> null ;
163+ };
164+
165+ if (targetMap == null ) {
166+ // We don't currently handle other "in" locations here (e.g., cookie).
167+ // Treat as "not validated" instead of failing.
168+ continue ;
169+ }
170+
171+ boolean present = targetMap .containsKey (name );
172+
173+ if (!present ) {
174+ // Try to satisfy the requirement using the OpenAPI default, if any
175+ Schema <?> schema = parameter .getSchema ();
176+ Object defaultValue = schema != null ? schema .getDefault () : null ;
177+
178+ if (defaultValue != null ) {
179+ targetMap .put (name , defaultValue );
180+ present = true ;
181+ }
182+ }
183+
184+ if (!present ) {
185+ if (!missing .isEmpty ()) {
186+ missing .append (", " );
187+ }
188+ missing .append (in ).append (" parameter '" ).append (name ).append ("'" );
189+ }
190+ }
191+
192+ if (!missing .isEmpty ()) {
193+ String operationId =
194+ operation .getOperation ().getOperationId () != null
195+ ? operation .getOperation ().getOperationId ()
196+ : "<unknown>" ;
197+ throw new IllegalArgumentException (
198+ "Missing required OpenAPI parameters for operation '" + operationId + "': " + missing );
199+ }
200+ }
127201}
0 commit comments