001/*
002 * Copyright (c) 2023 Chris K Wensel <[email protected]>. All Rights Reserved.
003 *
004 * This Source Code Form is subject to the terms of the Mozilla Public
005 * License, v. 2.0. If a copy of the MPL was not distributed with this
006 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
007 */
008
009package clusterless.commons.naming;
010
011import java.util.HashMap;
012import java.util.Map;
013import java.util.Objects;
014import java.util.Optional;
015
016/**
017 * Creates an identifier to use as an export/import key for provider output/export values.
018 * <p/>
019 * <pre>
020 * aws:qualifier:[stage:]scopeName:scopeVersion:resourceNS:resourceType:resourceName
021 * </pre>
022 * <p/>
023 * Where the qualifier represents the type of value being exported/imported:
024 * - id
025 * - name
026 * - arn
027 * <p/>
028 * Where stage is the stage of the deployment, such as dev, test, prod, etc. It is optional.
029 * <p/>
030 * scopeName and scopeVersion are analogous to project names and versions.
031 * <p/>
032 * Resources are identified by a namespace, type, and name.
033 * <p/>
034 *
035 * <pre>
036 *   ref:aws:id:project-a:20230101:core:compute:spot
037 *   ref:aws:id:dev:project-a:20230101:core:compute:spot
038 * </pre>
039 */
040public final class Ref {
041    public static Ref ref() {
042        return new Ref();
043    }
044
045    public static Ref idRef() {
046        return new Ref().withQualifier(Qualifier.Id);
047    }
048
049    public static Ref arnRef() {
050        return new Ref().withQualifier(Qualifier.Arn);
051    }
052
053    public static Ref nameRef() {
054        return new Ref().withQualifier(Qualifier.Name);
055    }
056
057    public static boolean isRef(String value) {
058        return value != null && value.startsWith("ref:");
059    }
060
061    public static Optional<Qualifier> qualifier(String value) {
062        if (!isRef(value)) {
063            return Optional.empty();
064        }
065
066        return Qualifier.lookup(value.split(":")[2]);
067    }
068
069    public static Optional<String> provider(String value) {
070        if (!isRef(value)) {
071            return Optional.empty();
072        }
073
074        return Optional.ofNullable(value.split(":")[1]);
075    }
076
077    final Fixed provider;
078    final Qualifier qualifier;
079    final Stage stage;
080    final Fixed scope;
081    final Version scopeVersion;
082    final Fixed resourceNs;
083    final Fixed resourceType;
084    final Fixed resourceName;
085
086    public Ref() {
087        provider = Fixed.fixedNull();
088        qualifier = null;
089        stage = Stage.nullStage();
090        scope = Fixed.fixedNull();
091        scopeVersion = Version.versionNull();
092        resourceNs = Fixed.fixedNull();
093        resourceType = Fixed.fixedNull();
094        resourceName = Fixed.fixedNull();
095    }
096
097    private Ref(Fixed provider, Qualifier qualifier, Stage stage, Fixed scope, Version scopeVersion, Fixed resourceNs, Fixed resourceType, Fixed resourceName) {
098        this.provider = provider;
099        this.qualifier = qualifier;
100        this.stage = stage == null ? Stage.nullStage() : stage.asLower();
101        this.scope = scope;
102        this.scopeVersion = scopeVersion;
103        this.resourceNs = resourceNs;
104        this.resourceType = resourceType;
105        this.resourceName = resourceName;
106    }
107
108    public Ref withProvider(String provider) {
109        Objects.requireNonNull(provider);
110        return withProvider(Label.of(provider));
111    }
112
113    public Ref withProvider(Label provider) {
114        Objects.requireNonNull(provider);
115        return withProvider(Fixed.of(provider.lowerHyphen()));
116    }
117
118    public Ref withProvider(Fixed provider) {
119        Label.requireNonEmpty(provider);
120        return new Ref(provider, qualifier, stage, scope, scopeVersion, resourceNs, resourceType, resourceName);
121    }
122
123    public Ref withStage(Stage stage) {
124        return new Ref(provider, qualifier, stage, scope, scopeVersion, resourceNs, resourceType, resourceName);
125    }
126
127    public Ref withScope(String scope) {
128        return withScope(Label.of(scope));
129    }
130
131    public Ref withScope(Label scope) {
132        return withScope(Fixed.of(scope.lowerHyphen()));
133    }
134
135    public Ref withScope(Fixed scope) {
136        return new Ref(provider, qualifier, stage, scope, scopeVersion, resourceNs, resourceType, resourceName);
137    }
138
139    public Ref withScopeVersion(String scopeVersion) {
140        return withScopeVersion(Version.of(scopeVersion));
141    }
142
143    public Ref withScopeVersion(Fixed scopeVersion) {
144        return withScopeVersion(Version.of(scopeVersion.lowerHyphen()));
145    }
146
147    public Ref withScopeVersion(Version scopeVersion) {
148        return new Ref(provider, qualifier, stage, scope, scopeVersion, resourceNs, resourceType, resourceName);
149    }
150
151    public Ref withResourceNs(String resourceNs) {
152        return withResourceNs(Label.of(resourceNs));
153    }
154
155    public Ref withResourceNs(Label resourceNs) {
156        return withResourceNs(Fixed.of(resourceNs.lowerHyphen()));
157    }
158
159    public Ref withResourceNs(Fixed resourceNs) {
160        return new Ref(provider, qualifier, stage, scope, scopeVersion, resourceNs, resourceType, resourceName);
161    }
162
163    public Ref withResourceType(String resourceType) {
164        return withResourceType(Label.of(resourceType));
165    }
166
167    public Ref withResourceType(Label resourceType) {
168        return withResourceType(Fixed.of(resourceType.lowerHyphen()));
169    }
170
171    public Ref withResourceType(Fixed resourceType) {
172        return new Ref(provider, qualifier, stage, scope, scopeVersion, resourceNs, resourceType, resourceName);
173    }
174
175    public Ref withResourceName(String resourceName) {
176        return withResourceName(Label.of(resourceName));
177    }
178
179    public Ref withResourceName(Label resourceName) {
180        return withResourceName(Fixed.of(resourceName.lowerHyphen()));
181    }
182
183    public Ref withResourceName(Fixed resourceName) {
184        return new Ref(provider, qualifier, stage, scope, scopeVersion, resourceNs, resourceType, resourceName);
185    }
186
187    public Ref withQualifier(Qualifier qualifier) {
188        return new Ref(provider, qualifier, stage, scope, scopeVersion, resourceNs, resourceType, resourceName);
189    }
190
191    public Label provider() {
192        return provider;
193    }
194
195    public Stage stage() {
196        return stage;
197    }
198
199    public Fixed scope() {
200        return scope;
201    }
202
203    public Fixed scopeVersion() {
204        return scopeVersion;
205    }
206
207    public Fixed resourceNs() {
208        return resourceNs;
209    }
210
211    public Fixed resourceType() {
212        return resourceType;
213    }
214
215    public Fixed resourceName() {
216        return resourceName;
217    }
218
219    public Qualifier qualifier() {
220        return qualifier;
221    }
222
223    public Label resourceLabel() {
224        requireNonNull(resourceNs, "resourceNs required");
225        requireNonNull(resourceType, "resourceType required");
226        requireNonNull(resourceName, "resourceName required");
227
228        return Label.NULL
229                .with(resourceNs)
230                .with(Label.of(resourceType.value()))
231                .with(resourceName);
232    }
233
234    public Label label() {
235        requireNonNull(provider, "provider required");
236        requireNonNull(qualifier, "qualifier required");
237        requireNonNull(scope, "scope required");
238        requireNonNull(scopeVersion, "scopeVersion required");
239        requireNonNull(resourceNs, "resourceNs required");
240        requireNonNull(resourceType, "resourceType required");
241        requireNonNull(resourceName, "resourceName required");
242
243        return Label.of("ref")
244                .with(provider)
245                .with(qualifier)
246                .with(stage)
247                .with(scope)
248                .with(scopeVersion)
249                .with(resourceNs)
250                .with(resourceType)
251                .with(resourceName);
252    }
253
254    private static void requireNonNull(Label label, String message) {
255        Objects.requireNonNull(label, message);
256
257        if (label.isNull()) {
258            throw new NullPointerException(message);
259        }
260    }
261
262
263    public String exportName() {
264        return label().lowerColonPath();
265    }
266
267    @Override
268    public String toString() {
269        return Label.of("ref")
270                .with(provider)
271                .with(qualifier)
272                .with(stage)
273                .with(scope)
274                .with(scopeVersion)
275                .with(resourceNs)
276                .with(resourceType)
277                .with(resourceName).lowerColonPath();
278    }
279
280    @Override
281    public boolean equals(Object o) {
282        if (this == o) return true;
283        if (o == null || getClass() != o.getClass()) return false;
284        Ref ref = (Ref) o;
285        return Objects.equals(provider, ref.provider) && Objects.equals(stage, ref.stage) && Objects.equals(scope, ref.scope) && Objects.equals(scopeVersion, ref.scopeVersion) && Objects.equals(resourceNs, ref.resourceNs) && Objects.equals(resourceType, ref.resourceType) && Objects.equals(resourceName, ref.resourceName) && qualifier == ref.qualifier;
286    }
287
288    @Override
289    public int hashCode() {
290        return Objects.hash(provider, stage, scope, scopeVersion, resourceNs, resourceType, resourceName, qualifier);
291    }
292
293    public enum Qualifier implements Label.EnumLabel {
294        Name,
295        Id,
296        Arn;
297
298        static final Map<String, Qualifier> map = new HashMap<>() {
299            {
300                put(Name.lowerHyphen(), Name);
301                put(Id.lowerHyphen(), Id);
302                put(Arn.lowerHyphen(), Arn);
303            }
304        };
305
306        public static Optional<Qualifier> lookup(String value) {
307            if (value == null) {
308                return null;
309            }
310
311            return Optional.ofNullable(map.get(value.toLowerCase()));
312        }
313    }
314}