Skip to content

Commit e5acec8

Browse files
authored
record unresolved $ref in OpenAPI (#1510)
1 parent a661051 commit e5acec8

23 files changed

Lines changed: 481 additions & 30 deletions

File tree

examples/asyncapi.mqtt.kafka.proxy/.github/test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ for i in $(seq 1 5); do
2424
break
2525
fi
2626

27-
sleep 2
27+
sleep 5
2828
done
2929

3030
OUTPUT=$(

examples/asyncapi.mqtt.proxy/.github/test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ for i in $(seq 1 5); do
2424
break
2525
fi
2626

27-
sleep 2
27+
sleep 5
2828
done
2929

3030
OUTPUT=$(

runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/composite/OpenapiCompositeGenerator.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
import java.io.StringReader;
2020
import java.io.StringWriter;
2121
import java.util.ArrayList;
22+
import java.util.Collection;
23+
import java.util.LinkedHashSet;
2224
import java.util.List;
2325
import java.util.Map;
2426
import java.util.Objects;
27+
import java.util.Set;
2528
import java.util.function.Consumer;
2629
import java.util.stream.Stream;
2730

@@ -67,6 +70,8 @@
6770

6871
public abstract class OpenapiCompositeGenerator
6972
{
73+
private final Set<String> unresolved = new LinkedHashSet<>();
74+
7075
public final OpenapiCompositeConfig generate(
7176
OpenapiBindingConfig binding)
7277
{
@@ -88,15 +93,22 @@ public final OpenapiCompositeConfig generate(
8893
specification.servers == null || specification.servers.isEmpty()
8994
? List.of(OpenapiServerConfig.builder().build())
9095
: specification.servers;
91-
final OpenapiView asyncapi = OpenapiView.of(tagIndex++, label, parser.parse(payload), configs);
96+
final OpenapiView openapi = OpenapiView.of(tagIndex++, label, parser.parse(payload), configs);
97+
98+
unresolved.addAll(openapi.unresolvedRefs());
9299

93-
schemas.add(new OpenapiSchemaConfig(label, schemaId, asyncapi));
100+
schemas.add(new OpenapiSchemaConfig(label, schemaId, openapi));
94101
}
95102
}
96103

97104
return generate(binding, schemas);
98105
}
99106

107+
public final Collection<String> unresolvedRefs()
108+
{
109+
return unresolved;
110+
}
111+
100112
protected abstract OpenapiCompositeConfig generate(
101113
OpenapiBindingConfig binding,
102114
List<OpenapiSchemaConfig> schemas);
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2021-2024 Aklivity Inc
3+
*
4+
* Licensed under the Aklivity Community License (the "License"); you may not use
5+
* this file except in compliance with the License. You may obtain a copy of the
6+
* License at
7+
*
8+
* https://www.aklivity.io/aklivity-community-license/
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OF ANY KIND, either express or implied. See the License for the
13+
* specific language governing permissions and limitations under the License.
14+
*/
15+
package io.aklivity.zilla.runtime.binding.openapi.internal.event;
16+
17+
import static io.aklivity.zilla.runtime.binding.openapi.internal.types.event.OpenapiEventType.UNRESOLVED_REF;
18+
19+
import java.nio.ByteBuffer;
20+
import java.time.Clock;
21+
22+
import org.agrona.concurrent.AtomicBuffer;
23+
import org.agrona.concurrent.UnsafeBuffer;
24+
25+
import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding;
26+
import io.aklivity.zilla.runtime.binding.openapi.internal.types.event.EventFW;
27+
import io.aklivity.zilla.runtime.binding.openapi.internal.types.event.OpenapiEventExFW;
28+
import io.aklivity.zilla.runtime.engine.EngineContext;
29+
import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
30+
31+
public class OpenapiEventContext
32+
{
33+
private static final int EVENT_BUFFER_CAPACITY = 1024;
34+
35+
private final AtomicBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY));
36+
private final AtomicBuffer extensionBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY));
37+
private final EventFW.Builder eventRW = new EventFW.Builder();
38+
private final OpenapiEventExFW.Builder openapiEventExRW = new OpenapiEventExFW.Builder();
39+
private final int openapiTypeId;
40+
private final int unresolvedRef;
41+
private final MessageConsumer eventWriter;
42+
private final Clock clock;
43+
44+
public OpenapiEventContext(
45+
EngineContext context)
46+
{
47+
this.openapiTypeId = context.supplyTypeId(OpenapiBinding.NAME);
48+
this.unresolvedRef = context.supplyEventId("binding.openapi.unresolved.ref");
49+
this.eventWriter = context.supplyEventWriter();
50+
this.clock = context.clock();
51+
}
52+
53+
public void unresolvedRef(
54+
long bindingId,
55+
String ref)
56+
{
57+
OpenapiEventExFW extension = openapiEventExRW
58+
.wrap(extensionBuffer, 0, extensionBuffer.capacity())
59+
.unresolvedRef(e -> e
60+
.typeId(UNRESOLVED_REF.value())
61+
.ref(ref)
62+
)
63+
.build();
64+
EventFW event = eventRW
65+
.wrap(eventBuffer, 0, eventBuffer.capacity())
66+
.id(unresolvedRef)
67+
.timestamp(clock.millis())
68+
.traceId(0L)
69+
.namespacedId(bindingId)
70+
.extension(extension.buffer(), extension.offset(), extension.limit())
71+
.build();
72+
eventWriter.accept(openapiTypeId, event.buffer(), event.offset(), event.limit());
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2021-2024 Aklivity Inc
3+
*
4+
* Licensed under the Aklivity Community License (the "License"); you may not use
5+
* this file except in compliance with the License. You may obtain a copy of the
6+
* License at
7+
*
8+
* https://www.aklivity.io/aklivity-community-license/
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OF ANY KIND, either express or implied. See the License for the
13+
* specific language governing permissions and limitations under the License.
14+
*/
15+
package io.aklivity.zilla.runtime.binding.openapi.internal.event;
16+
17+
import org.agrona.DirectBuffer;
18+
19+
import io.aklivity.zilla.runtime.binding.openapi.internal.types.String16FW;
20+
import io.aklivity.zilla.runtime.binding.openapi.internal.types.event.EventFW;
21+
import io.aklivity.zilla.runtime.binding.openapi.internal.types.event.OpenapiEventExFW;
22+
import io.aklivity.zilla.runtime.binding.openapi.internal.types.event.OpenapiUnresolvedRefExFW;
23+
import io.aklivity.zilla.runtime.engine.Configuration;
24+
import io.aklivity.zilla.runtime.engine.event.EventFormatterSpi;
25+
26+
public final class OpenapiEventFormatter implements EventFormatterSpi
27+
{
28+
private final EventFW eventRO = new EventFW();
29+
private final OpenapiEventExFW openapiEventExRO = new OpenapiEventExFW();
30+
31+
OpenapiEventFormatter(
32+
Configuration config)
33+
{
34+
}
35+
36+
public String format(
37+
DirectBuffer buffer,
38+
int index,
39+
int length)
40+
{
41+
final EventFW event = eventRO.wrap(buffer, index, index + length);
42+
final OpenapiEventExFW extension = openapiEventExRO
43+
.wrap(event.extension().buffer(), event.extension().offset(), event.extension().limit());
44+
String result = null;
45+
switch (extension.kind())
46+
{
47+
case UNRESOLVED_REF:
48+
{
49+
OpenapiUnresolvedRefExFW ex = extension.unresolvedRef();
50+
result = String.format("Unresolved reference (%s).", asString(ex.ref()));
51+
break;
52+
}
53+
}
54+
return result;
55+
}
56+
57+
private static String asString(
58+
String16FW stringFW)
59+
{
60+
String s = stringFW.asString();
61+
return s == null ? "" : s;
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2021-2024 Aklivity Inc
3+
*
4+
* Licensed under the Aklivity Community License (the "License"); you may not use
5+
* this file except in compliance with the License. You may obtain a copy of the
6+
* License at
7+
*
8+
* https://www.aklivity.io/aklivity-community-license/
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OF ANY KIND, either express or implied. See the License for the
13+
* specific language governing permissions and limitations under the License.
14+
*/
15+
package io.aklivity.zilla.runtime.binding.openapi.internal.event;
16+
17+
import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding;
18+
import io.aklivity.zilla.runtime.engine.Configuration;
19+
import io.aklivity.zilla.runtime.engine.event.EventFormatterFactorySpi;
20+
21+
public final class OpenapiEventFormatterFactory implements EventFormatterFactorySpi
22+
{
23+
@Override
24+
public OpenapiEventFormatter create(
25+
Configuration config)
26+
{
27+
return new OpenapiEventFormatter(config);
28+
}
29+
30+
@Override
31+
public String type()
32+
{
33+
return OpenapiBinding.NAME;
34+
}
35+
}

runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/resolver/AbstractOpenapiResolver.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package io.aklivity.zilla.runtime.binding.openapi.internal.model.resolver;
1616

1717
import java.util.Map;
18+
import java.util.Set;
1819
import java.util.regex.Matcher;
1920
import java.util.regex.Pattern;
2021

@@ -24,20 +25,28 @@ public abstract class AbstractOpenapiResolver<T extends AbstractOpenapiResolvabl
2425
{
2526
private final Map<String, T> resolvables;
2627
private final Matcher matcher;
28+
private final Set<String> unresolved;
2729

2830
public AbstractOpenapiResolver(
2931
Map<String, T> resolvables,
30-
Pattern pattern)
32+
Pattern pattern,
33+
Set<String> unresolved)
3134
{
3235
this.resolvables = resolvables;
3336
this.matcher = pattern.matcher("");
37+
this.unresolved = unresolved;
3438
}
3539

3640
public final T resolve(
3741
T model)
3842
{
3943
final String key = resolveRef(model.ref);
40-
return key != null ? resolvables.get(key) : model;
44+
T candidate = key != null ? resolvables.getOrDefault(key, model) : model;
45+
if (candidate.ref != null)
46+
{
47+
unresolved.add(candidate.ref);
48+
}
49+
return candidate;
4150
}
4251

4352
public T resolve(

runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/resolver/OpenapiHeaderResolver.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import java.util.Map;
1818
import java.util.Optional;
19+
import java.util.Set;
1920
import java.util.regex.Pattern;
2021

2122
import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi;
@@ -24,12 +25,14 @@
2425
public final class OpenapiHeaderResolver extends AbstractOpenapiResolver<OpenapiHeader>
2526
{
2627
public OpenapiHeaderResolver(
27-
Openapi model)
28+
Openapi model,
29+
Set<String> unresolved)
2830
{
2931
super(
3032
Optional.ofNullable(model.components)
3133
.map(c -> c.headers)
3234
.orElseGet(Map::of),
33-
Pattern.compile("#/components/headers/(.+)"));
35+
Pattern.compile("#/components/headers/(.+)"),
36+
unresolved);
3437
}
3538
}

runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/resolver/OpenapiLinkResolver.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import java.util.Map;
1818
import java.util.Optional;
19+
import java.util.Set;
1920
import java.util.regex.Pattern;
2021

2122
import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi;
@@ -24,12 +25,14 @@
2425
public final class OpenapiLinkResolver extends AbstractOpenapiResolver<OpenapiLink>
2526
{
2627
public OpenapiLinkResolver(
27-
Openapi model)
28+
Openapi model,
29+
Set<String> unresolved)
2830
{
2931
super(
3032
Optional.ofNullable(model.components)
3133
.map(c -> c.links)
3234
.orElseGet(Map::of),
33-
Pattern.compile("#/components/links/(.+)"));
35+
Pattern.compile("#/components/links/(.+)"),
36+
unresolved);
3437
}
3538
}

runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/resolver/OpenapiParameterResolver.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import java.util.Map;
1818
import java.util.Optional;
19+
import java.util.Set;
1920
import java.util.regex.Pattern;
2021

2122
import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi;
@@ -24,12 +25,14 @@
2425
public final class OpenapiParameterResolver extends AbstractOpenapiResolver<OpenapiParameter>
2526
{
2627
public OpenapiParameterResolver(
27-
Openapi model)
28+
Openapi model,
29+
Set<String> unresolved)
2830
{
2931
super(
3032
Optional.ofNullable(model.components)
3133
.map(c -> c.parameters)
3234
.orElseGet(Map::of),
33-
Pattern.compile("#/components/parameters/(.+)"));
35+
Pattern.compile("#/components/parameters/(.+)"),
36+
unresolved);
3437
}
3538
}

0 commit comments

Comments
 (0)