|
1 | 1 | package com.launchdarkly.sdk.server.ai; |
2 | 2 |
|
| 3 | +import static java.util.Arrays.binarySearch; |
| 4 | + |
3 | 5 | import java.util.ArrayList; |
4 | 6 | import java.util.HashMap; |
5 | 7 | import java.util.List; |
@@ -43,113 +45,114 @@ public LDAiClient(LDClientInterface client) { |
43 | 45 |
|
44 | 46 | /** |
45 | 47 | * Method to convert the JSON variable into the AiConfig object |
| 48 | + * |
| 49 | + * If the parsing failed, the code will log an error and |
| 50 | + * return a well formed but with nullable value nulled and disabled AIConfig |
| 51 | + * |
| 52 | + * Doing all the error checks, so if somehow LD backend return incorrect value types, there is logging |
| 53 | + * This also opens up the possibility of allowing customer to build this using a JSON string in the future |
46 | 54 | * |
47 | 55 | * @param value |
48 | 56 | * @param key |
49 | 57 | */ |
50 | 58 | protected AiConfig parseAiConfig(LDValue value, String key) { |
51 | | - AiConfig result = new AiConfig(); |
52 | | - |
53 | | - try { |
54 | | - // Verify the whole value is a JSON object |
55 | | - if (value == null || value.getType() != LDValueType.OBJECT) { |
56 | | - throw new AiConfigParseException("Input to parseAiConfig must be a JSON object"); |
57 | | - } |
58 | | - |
59 | | - // Convert the _meta JSON object into Meta |
60 | | - LDValue valueMeta = value.get("_ldMeta"); |
61 | | - if (valueMeta == LDValue.ofNull() || valueMeta.getType() != LDValueType.OBJECT) { |
62 | | - throw new AiConfigParseException("_ldMeta must be a JSON object"); |
63 | | - } |
| 59 | + boolean enabled = false; |
64 | 60 |
|
65 | | - // Create Meta using constructor |
66 | | - Meta meta = new Meta( |
67 | | - ldValueNullCheck(valueMeta.get("variationKey")).stringValue(), |
68 | | - Optional.of(valueMeta.get("version").intValue()), |
69 | | - ldValueNullCheck(valueMeta.get("enabled")).booleanValue() |
70 | | - ); |
71 | | - result.setMeta(meta); |
72 | | - |
73 | | - // Convert the optional messages from an JSON array of JSON objects into Message |
74 | | - // Q: Does it even make sense to have 0 messages? |
75 | | - LDValue valueMessages = value.get("messages"); |
76 | | - if (valueMeta == LDValue.ofNull() || valueMessages.getType() != LDValueType.ARRAY) { |
77 | | - throw new AiConfigParseException("messages must be a JSON array"); |
78 | | - } |
79 | | - |
80 | | - List<Message> messages = new ArrayList<Message>(); |
81 | | - valueMessages.valuesAs(new Message.MessageConverter()); |
82 | | - for (Message message : valueMessages.valuesAs(new Message.MessageConverter())) { |
83 | | - messages.add(message); |
84 | | - } |
85 | | - result.setMessages(messages); |
| 61 | + // Verify the whole value is a JSON object |
| 62 | + if(!checkValueWithFailureLogging(value, LDValueType.OBJECT, logger, "Input to parseAiConfig must be a JSON object")) { |
| 63 | + return new AiConfig(enabled, null, null, null, null); |
| 64 | + } |
86 | 65 |
|
87 | | - // Convert the optional model from an JSON object of with parameters and custom |
88 | | - // into Model |
89 | | - LDValue valueModel = value.get("model"); |
90 | | - if (valueModel == LDValue.ofNull() || valueModel.getType() != LDValueType.OBJECT) { |
91 | | - throw new AiConfigParseException("model must be a JSON object"); |
92 | | - } |
| 66 | + // Convert the _meta JSON object into Meta |
| 67 | + LDValue valueMeta = value.get("_ldMeta"); |
| 68 | + if (!checkValueWithFailureLogging(valueMeta, LDValueType.OBJECT, logger, "_ldMeta must be a JSON object")) { |
| 69 | + // Q: If we can't read _meta, enabled by spec would be defaulted to false. Does it even matter the rest of the values? |
| 70 | + return new AiConfig(enabled, null, null, null, null); |
| 71 | + } |
93 | 72 |
|
94 | | - // Prepare parameters and custom maps for Model |
95 | | - String modelName = ldValueNullCheck(valueModel.get("name")).stringValue(); |
96 | | - HashMap<String, LDValue> parameters = null; |
97 | | - HashMap<String, LDValue> custom = null; |
| 73 | + // The booleanValue will get false if that value something that we are not expecting, which is good |
| 74 | + enabled = valueMeta.get("enabled").booleanValue(); |
98 | 75 |
|
99 | | - LDValue valueParameters = valueModel.get("parameters"); |
100 | | - if (valueParameters.getType() != LDValueType.NULL) { |
101 | | - if (valueParameters.getType() != LDValueType.OBJECT) { |
102 | | - throw new AiConfigParseException("non-null parameters must be a JSON object"); |
| 76 | + String variationKey = null; |
| 77 | + if (checkValueWithFailureLogging(valueMeta.get("variationKey"), LDValueType.STRING, logger, "variationKey should be a string")) { |
| 78 | + variationKey = valueMeta.get("variationKey").stringValue(); |
| 79 | + } |
| 80 | + // Create Meta using constructor |
| 81 | + Meta meta = new Meta( |
| 82 | + variationKey, |
| 83 | + Optional.of(valueMeta.get("version").intValue()) |
| 84 | + ); |
| 85 | + |
| 86 | + // Convert the optional model from an JSON object of with parameters and custom |
| 87 | + // into Model |
| 88 | + Model model = null; |
| 89 | + |
| 90 | + LDValue valueModel = value.get("model"); |
| 91 | + if (checkValueWithFailureLogging(valueModel, LDValueType.OBJECT, logger, "model if exists must be a JSON object")) { |
| 92 | + if (checkValueWithFailureLogging(valueModel.get("name"), LDValueType.STRING, logger, "model name must be a string and is required")) { |
| 93 | + String modelName = valueModel.get("name").stringValue(); |
| 94 | + |
| 95 | + // Prepare parameters and custom maps for Model |
| 96 | + HashMap<String, LDValue> parameters = null; |
| 97 | + HashMap<String, LDValue> custom = null; |
| 98 | + |
| 99 | + LDValue valueParameters = valueModel.get("parameters"); |
| 100 | + if (checkValueWithFailureLogging(valueParameters, LDValueType.OBJECT, logger, "non-null parameters must be a JSON object")) { |
| 101 | + parameters = new HashMap<>(); |
| 102 | + for (String k : valueParameters.keys()) { |
| 103 | + parameters.put(k, valueParameters.get(k)); |
| 104 | + } |
103 | 105 | } |
104 | | - |
105 | | - parameters = new HashMap<>(); |
106 | | - for (String k : valueParameters.keys()) { |
107 | | - parameters.put(k, valueParameters.get(k)); |
| 106 | + |
| 107 | + LDValue valueCustom = valueModel.get("custom"); |
| 108 | + if (checkValueWithFailureLogging(valueCustom, LDValueType.OBJECT, logger, "non-null custom must be a JSON object")) { |
| 109 | + |
| 110 | + custom = new HashMap<>(); |
| 111 | + for (String k : valueCustom.keys()) { |
| 112 | + custom.put(k, valueCustom.get(k)); |
| 113 | + } |
108 | 114 | } |
| 115 | + |
| 116 | + model = new Model(modelName, parameters, custom); |
109 | 117 | } |
| 118 | + } |
110 | 119 |
|
111 | | - LDValue valueCustom = valueModel.get("custom"); |
112 | | - if (valueCustom.getType() != LDValueType.NULL) { |
113 | | - if (valueCustom.getType() != LDValueType.OBJECT) { |
114 | | - throw new AiConfigParseException("non-null custom must be a JSON object"); |
115 | | - } |
| 120 | + // Convert the optional messages from an JSON array of JSON objects into Message |
| 121 | + // Q: Does it even make sense to have 0 messages? |
| 122 | + List<Message> messages = null; |
116 | 123 |
|
117 | | - custom = new HashMap<>(); |
118 | | - for (String k : valueCustom.keys()) { |
119 | | - custom.put(k, valueCustom.get(k)); |
120 | | - } |
| 124 | + LDValue valueMessages = value.get("messages"); |
| 125 | + if (checkValueWithFailureLogging(valueMessages, LDValueType.ARRAY, logger, "messages if exists must be a JSON array")) { |
| 126 | + messages = new ArrayList<Message>(); |
| 127 | + valueMessages.valuesAs(new Message.MessageConverter()); |
| 128 | + for (Message message : valueMessages.valuesAs(new Message.MessageConverter())) { |
| 129 | + messages.add(message); |
121 | 130 | } |
122 | | - |
123 | | - // Create Model using constructor |
124 | | - Model model = new Model(modelName, parameters, custom); |
125 | | - result.setModel(model); |
126 | | - |
127 | | - // Convert the optional provider from an JSON object of with name into Provider |
128 | | - LDValue valueProvider = value.get("provider"); |
129 | | - if (valueProvider.getType() != LDValueType.NULL) { |
130 | | - if (valueProvider.getType() != LDValueType.OBJECT) { |
131 | | - throw new AiConfigParseException("non-null provider must be a JSON object"); |
132 | | - } |
| 131 | + } |
133 | 132 |
|
134 | | - Provider provider = new Provider(ldValueNullCheck(valueProvider.get("name")).stringValue()); |
135 | | - result.setProvider(provider); |
136 | | - } else { |
137 | | - // Provider is optional - we can just set null and proceed |
138 | | - result.setProvider(null); |
| 133 | + // Convert the optional provider from an JSON object of with name into Provider |
| 134 | + LDValue valueProvider = value.get("provider"); |
| 135 | + String providerName = null; |
| 136 | + |
| 137 | + if(checkValueWithFailureLogging(valueProvider, LDValueType.OBJECT, logger, "non-null provider must be a JSON object")) { |
| 138 | + if(checkValueWithFailureLogging(valueProvider.get("name"), LDValueType.STRING, logger, "provider name must be a String")) { |
| 139 | + providerName = valueProvider.get("name").stringValue(); |
139 | 140 | } |
140 | | - } catch (AiConfigParseException e) { |
141 | | - // logger.error(e.getMessage()); |
142 | | - return null; |
143 | 141 | } |
144 | 142 |
|
145 | | - return result; |
| 143 | + Provider provider = new Provider(providerName); |
| 144 | + |
| 145 | + return new AiConfig(enabled, meta, model, messages, provider); |
146 | 146 | } |
147 | 147 |
|
148 | | - protected <T> T ldValueNullCheck(T ldValue) throws AiConfigParseException { |
149 | | - if (ldValue == LDValue.ofNull()) { |
150 | | - throw new AiConfigParseException("Unexpected Null value for non-optional field"); |
| 148 | + protected boolean checkValueWithFailureLogging(LDValue ldValue, LDValueType expectedType, LDLogger logger, String message) { |
| 149 | + if (ldValue.getType() != expectedType) { |
| 150 | + if (logger != null) { |
| 151 | + logger.error(message); |
| 152 | + } |
| 153 | + return false; |
151 | 154 | } |
152 | | - return ldValue; |
| 155 | + return true; |
153 | 156 | } |
154 | 157 |
|
155 | 158 | class AiConfigParseException extends Exception { |
|
0 commit comments