use of net.morimekta.providence.descriptor.PMessageDescriptor in project providence by morimekta.
the class JsonSerializer method parseMapKey.
private Object parseMapKey(String key, PDescriptor keyType) throws SerializerException {
try {
switch(keyType.getType()) {
case BOOL:
if (key.equalsIgnoreCase("true")) {
return Boolean.TRUE;
} else if (key.equalsIgnoreCase("false")) {
return Boolean.FALSE;
}
throw new SerializerException("Invalid boolean value: \"" + Strings.escape(key) + "\"");
case BYTE:
return Byte.parseByte(key);
case I16:
return Short.parseShort(key);
case I32:
return Integer.parseInt(key);
case I64:
return Long.parseLong(key);
case DOUBLE:
try {
JsonTokenizer tokenizer = new JsonTokenizer(new ByteArrayInputStream(key.getBytes(StandardCharsets.US_ASCII)));
JsonToken token = tokenizer.next();
if (!token.isNumber()) {
throw new SerializerException("Unable to parse double from key \"" + key + "\"");
} else if (tokenizer.hasNext()) {
throw new SerializerException("Garbage after double: \"" + key + "\"");
}
return token.doubleValue();
} catch (SerializerException e) {
throw e;
} catch (JsonException | IOException e) {
throw new SerializerException(e, "Unable to parse double from key \"" + key + "\"");
}
case STRING:
return key;
case BINARY:
try {
return Binary.fromBase64(key);
} catch (IllegalArgumentException e) {
throw new SerializerException(e, "Unable to parse Base64 data");
}
case ENUM:
PEnumBuilder<?> eb = ((PEnumDescriptor<?>) keyType).builder();
if (Strings.isInteger(key)) {
eb.setById(Integer.parseInt(key));
} else {
eb.setByName(key);
}
if (readStrict && !eb.valid()) {
throw new SerializerException("\"%s\" is not a known enum value for %s", Strings.escape(key), keyType.getQualifiedName());
}
return eb.build();
case MESSAGE:
PMessageDescriptor<?, ?> st = (PMessageDescriptor<?, ?>) keyType;
if (!st.isSimple()) {
throw new SerializerException("Only simple structs can be used as map key. %s is not.", st.getQualifiedName());
}
ByteArrayInputStream input = new ByteArrayInputStream(key.getBytes(StandardCharsets.UTF_8));
try {
JsonTokenizer tokenizer = new JsonTokenizer(input);
if (JsonToken.kMapStart == tokenizer.expectSymbol("message start", JsonToken.kMapStart, JsonToken.kListStart)) {
return parseMessage(tokenizer, st);
} else {
return parseCompactMessage(tokenizer, st);
}
} catch (JsonException | IOException e) {
throw new SerializerException(e, "Error parsing message key: " + e.getMessage());
}
default:
throw new SerializerException("Illegal key type: %s", keyType.getType());
}
} catch (NumberFormatException nfe) {
throw new SerializerException(nfe, "Unable to parse numeric value %s", key);
}
}
use of net.morimekta.providence.descriptor.PMessageDescriptor in project providence by morimekta.
the class ProgramConverter method convert.
/**
* Convert document model to declared document.
*
* @param path The program file path.
* @param path Path of the program file to convert.
* @param program Program model to convert.
* @return The declared thrift document.
*/
public CProgram convert(String path, ProgramType program) {
ImmutableList.Builder<PDeclaredDescriptor<?>> declaredTypes = ImmutableList.builder();
ImmutableList.Builder<CConst> constants = ImmutableList.builder();
ImmutableMap.Builder<String, String> typedefs = ImmutableMap.builder();
ImmutableList.Builder<CService> services = ImmutableList.builder();
RecursiveTypeRegistry registry = programRegistry.registryForPath(path);
File dir = new File(path).getParentFile();
if (program.hasIncludes()) {
for (String include : program.getIncludes()) {
String includePath = new File(dir, include).getPath();
registry.registerInclude(ReflectionUtils.programNameFromPath(include), programRegistry.registryForPath(includePath));
}
}
for (Declaration decl : program.getDecl()) {
switch(decl.unionField()) {
case DECL_ENUM:
{
EnumType enumType = decl.getDeclEnum();
int nextValue = PEnumDescriptor.DEFAULT_FIRST_VALUE;
CEnumDescriptor type = new CEnumDescriptor(enumType.getDocumentation(), program.getProgramName(), enumType.getName(), enumType.getAnnotations());
List<CEnumValue> values = new ArrayList<>();
for (EnumValue value : enumType.getValues()) {
int v = value.hasId() ? value.getId() : nextValue;
nextValue = v + 1;
values.add(new CEnumValue(value.getDocumentation(), value.getId(), value.getName(), type, value.getAnnotations()));
}
type.setValues(values);
declaredTypes.add(type);
registry.register(type);
break;
}
case DECL_STRUCT:
{
MessageType messageType = decl.getDeclStruct();
List<CField> fields = new ArrayList<>();
if (messageType.hasFields()) {
fields.addAll(messageType.getFields().stream().map(field -> makeField(registry, program.getProgramName(), field, messageType.getVariant())).collect(Collectors.toList()));
}
PMessageDescriptor<?, ?> type;
switch(messageType.getVariant()) {
case STRUCT:
type = new CStructDescriptor(messageType.getDocumentation(), program.getProgramName(), messageType.getName(), fields, messageType.getAnnotations());
break;
case UNION:
type = new CUnionDescriptor(messageType.getDocumentation(), program.getProgramName(), messageType.getName(), fields, messageType.getAnnotations());
break;
case EXCEPTION:
type = new CExceptionDescriptor(messageType.getDocumentation(), program.getProgramName(), messageType.getName(), fields, messageType.getAnnotations());
break;
default:
throw new UnsupportedOperationException("Unhandled message variant " + messageType.getVariant());
}
declaredTypes.add(type);
registry.register(type);
break;
}
case DECL_CONST:
{
ConstType constant = decl.getDeclConst();
constants.add(makeConst(registry, program.getProgramName(), constant));
break;
}
case DECL_TYPEDEF:
{
typedefs.put(decl.getDeclTypedef().getName(), decl.getDeclTypedef().getType());
registry.registerTypedef(decl.getDeclTypedef().getName(), program.getProgramName(), decl.getDeclTypedef().getType());
break;
}
case DECL_SERVICE:
{
ServiceType serviceType = decl.getDeclService();
ImmutableList.Builder<CServiceMethod> methodBuilder = ImmutableList.builder();
if (serviceType.hasMethods()) {
for (FunctionType sm : serviceType.getMethods()) {
List<CField> rqFields = new ArrayList<>();
if (sm.numParams() > 0) {
for (FieldType field : sm.getParams()) {
rqFields.add(makeField(registry, program.getProgramName(), field, MessageVariant.STRUCT));
}
}
CStructDescriptor request = new CStructDescriptor(null, program.getProgramName(), serviceType.getName() + '.' + sm.getName() + ".request", rqFields, null);
CUnionDescriptor response = null;
if (!sm.isOneWay()) {
List<CField> rsFields = new ArrayList<>();
CField success;
if (sm.getReturnType() != null) {
PDescriptorProvider type = registry.getProvider(sm.getReturnType(), program.getProgramName(), sm.getAnnotations());
success = new CField(null, 0, PRequirement.OPTIONAL, "success", type, null, null);
} else {
success = new CField(null, 0, PRequirement.OPTIONAL, "success", PPrimitive.VOID.provider(), null, null);
}
rsFields.add(success);
if (sm.numExceptions() > 0) {
for (FieldType field : sm.getExceptions()) {
rsFields.add(makeField(registry, program.getProgramName(), field, MessageVariant.UNION));
}
}
response = new CUnionDescriptor(null, program.getProgramName(), serviceType.getName() + '.' + sm.getName() + ".response", rsFields, null);
}
CServiceMethod method = new CServiceMethod(sm.getDocumentation(), sm.getName(), sm.isOneWay(), request, response, sm.getAnnotations());
methodBuilder.add(method);
}
// for each method
}
// if has methods
PServiceProvider extendsProvider = null;
if (serviceType.hasExtend()) {
extendsProvider = registry.getServiceProvider(serviceType.getExtend(), program.getProgramName());
}
CService service = new CService(serviceType.getDocumentation(), program.getProgramName(), serviceType.getName(), extendsProvider, methodBuilder.build(), serviceType.getAnnotations());
services.add(service);
registry.registerRecursively(service);
}
}
}
return new CProgram(path, program.getDocumentation(), program.getProgramName(), program.getNamespaces(), getIncludedProgramNames(program), program.getIncludes(), typedefs.build(), declaredTypes.build(), services.build(), constants.build());
}
use of net.morimekta.providence.descriptor.PMessageDescriptor in project providence by morimekta.
the class ProvidenceConfigParser method parseConfigRecursively.
@SuppressWarnings("unchecked")
<M extends PMessage<M, F>, F extends PField> Pair<M, Set<String>> parseConfigRecursively(@Nonnull Path file, M parent, String[] stack) throws IOException {
Tokenizer tokenizer;
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file.toFile()))) {
// Non-enclosed content, meaning we should read the whole file immediately.
tokenizer = new Tokenizer(new Utf8StreamReader(in), Tokenizer.DEFAULT_BUFFER_SIZE, true);
}
ProvidenceConfigContext context = new ProvidenceConfigContext();
Set<String> includedFilePaths = new TreeSet<>();
includedFilePaths.add(canonicalFileLocation(file).toString());
Stage lastStage = Stage.INCLUDES;
M result = null;
Token token = tokenizer.peek();
while (token != null) {
tokenizer.next();
if (lastStage == Stage.MESSAGE) {
throw new TokenizerException(token, "Unexpected token '" + token.asString() + "', expected end of file.").setLine(tokenizer.getLine());
} else if (INCLUDE.equals(token.asString())) {
// if include && stage == INCLUDES --> INCLUDES
if (lastStage != Stage.INCLUDES) {
throw new TokenizerException(token, "Include added after defines or message. Only one def block allowed.").setLine(tokenizer.getLine());
}
token = tokenizer.expectLiteral("file to be included");
String includedFilePath = token.decodeLiteral(strict);
PMessage included;
Path includedFile;
try {
includedFile = resolveFile(file, includedFilePath);
Pair<PMessage, Set<String>> tmp = checkAndParseInternal(includedFile, null, stack);
if (tmp != null) {
includedFilePaths.add(includedFile.toString());
includedFilePaths.addAll(tmp.second);
included = tmp.first;
} else {
included = null;
}
} catch (FileNotFoundException e) {
throw new TokenizerException(token, "Included file \"%s\" not found.", includedFilePath).setLine(tokenizer.getLine());
}
token = tokenizer.expectIdentifier("the token 'as'");
if (!AS.equals(token.asString())) {
throw new TokenizerException(token, "Expected token 'as' after included file \"%s\".", includedFilePath).setLine(tokenizer.getLine());
}
token = tokenizer.expectIdentifier("Include alias");
String alias = token.asString();
if (RESERVED_WORDS.contains(alias)) {
throw new TokenizerException(token, "Alias \"%s\" is a reserved word.", alias).setLine(tokenizer.getLine());
}
if (context.containsReference(alias)) {
throw new TokenizerException(token, "Alias \"%s\" is already used.", alias).setLine(tokenizer.getLine());
}
context.setInclude(alias, included);
} else if (DEF.equals(token.asString())) {
// if params && stage == DEF --> DEF
lastStage = Stage.DEFINES;
parseDefinitions(context, tokenizer);
} else if (token.isQualifiedIdentifier()) {
// if a.b (type identifier) --> MESSAGE
lastStage = Stage.MESSAGE;
PMessageDescriptor<M, F> descriptor;
try {
descriptor = (PMessageDescriptor) registry.getDeclaredType(token.asString());
} catch (IllegalArgumentException e) {
// even in non-strict mode.
if (strict || stack.length == 1) {
throw new TokenizerException(token, "Unknown declared type: %s", token.asString()).setLine(tokenizer.getLine());
}
return null;
}
result = parseConfigMessage(tokenizer, context, descriptor.builder(), parent, file);
} else {
throw new TokenizerException(token, "Unexpected token '" + token.asString() + "'. Expected include, defines or message type").setLine(tokenizer.getLine());
}
token = tokenizer.peek();
}
if (result == null) {
throw new TokenizerException("No message in config: " + file.getFileName().toString());
}
return Pair.create(result, includedFilePaths);
}
use of net.morimekta.providence.descriptor.PMessageDescriptor in project providence by morimekta.
the class ProvidenceConfigParser method parseFieldValue.
@SuppressWarnings("unchecked")
Object parseFieldValue(Token next, Tokenizer tokenizer, ProvidenceConfigContext context, PDescriptor descriptor, boolean requireEnumValue) throws IOException {
try {
switch(descriptor.getType()) {
case BOOL:
if (TRUE.equals(next.asString())) {
return true;
} else if (FALSE.equals(next.asString())) {
return false;
} else if (next.isReferenceIdentifier()) {
return resolve(context, next, tokenizer, descriptor);
}
break;
case BYTE:
if (next.isReferenceIdentifier()) {
return resolve(context, next, tokenizer, descriptor);
} else if (next.isInteger()) {
return (byte) next.parseInteger();
}
break;
case I16:
if (next.isReferenceIdentifier()) {
return resolve(context, next, tokenizer, descriptor);
} else if (next.isInteger()) {
return (short) next.parseInteger();
}
break;
case I32:
if (next.isReferenceIdentifier()) {
return resolve(context, next, tokenizer, descriptor);
} else if (next.isInteger()) {
return (int) next.parseInteger();
}
break;
case I64:
if (next.isReferenceIdentifier()) {
return resolve(context, next, tokenizer, descriptor);
} else if (next.isInteger()) {
return next.parseInteger();
}
break;
case DOUBLE:
if (next.isReferenceIdentifier()) {
return resolve(context, next, tokenizer, descriptor);
} else if (next.isInteger() || next.isReal()) {
return next.parseDouble();
}
break;
case STRING:
if (next.isReferenceIdentifier()) {
return resolve(context, next, tokenizer, descriptor);
} else if (next.isStringLiteral()) {
return next.decodeLiteral(strict);
}
break;
case BINARY:
if (Token.B64.equals(next.asString())) {
tokenizer.expectSymbol("binary data enclosing start", Token.kParamsStart);
return Binary.fromBase64(tokenizer.readBinary(Token.kParamsEnd));
} else if (Token.HEX.equals(next.asString())) {
tokenizer.expectSymbol("binary data enclosing start", Token.kParamsStart);
return Binary.fromHexString(tokenizer.readBinary(Token.kParamsEnd));
} else if (next.isReferenceIdentifier()) {
return resolve(context, next, tokenizer, descriptor);
}
break;
case ENUM:
{
PEnumDescriptor ed = (PEnumDescriptor) descriptor;
PEnumValue value;
String name = next.asString();
if (next.isInteger()) {
value = ed.findById((int) next.parseInteger());
} else if (next.isIdentifier()) {
value = ed.findByName(name);
if (value == null && context.containsReference(name)) {
value = resolve(context, next, tokenizer, ed);
}
} else if (next.isReferenceIdentifier()) {
value = resolve(context, next, tokenizer, descriptor);
} else {
break;
}
if (value == null && (strict || requireEnumValue)) {
PEnumValue option = null;
if (next.isIdentifier()) {
for (PEnumValue o : ed.getValues()) {
if (o.getName().equalsIgnoreCase(name)) {
option = o;
break;
}
}
}
if (option != null) {
throw new TokenizerException(next, "No such enum value '%s' for %s, did you mean '%s'?", name, ed.getQualifiedName(), option.getName()).setLine(tokenizer.getLine());
}
throw new TokenizerException(next, "No such enum value '%s' for %s.", name, ed.getQualifiedName()).setLine(tokenizer.getLine());
}
return value;
}
case MESSAGE:
if (next.isReferenceIdentifier()) {
return resolve(context, next, tokenizer, descriptor);
} else if (next.isSymbol(Token.kMessageStart)) {
return parseMessage(tokenizer, context, ((PMessageDescriptor) descriptor).builder());
}
break;
case MAP:
{
if (next.isReferenceIdentifier()) {
Map resolved;
try {
// Make sure the reference is to a map.
resolved = resolve(context, next, tokenizer, descriptor);
} catch (ClassCastException e) {
throw new TokenizerException(next, "Reference %s is not a map field ", next.asString()).setLine(tokenizer.getLine());
}
return resolved;
} else if (next.isSymbol(Token.kMessageStart)) {
return parseMapValue(tokenizer, context, (PMap) descriptor, new LinkedHashMap());
}
break;
}
case SET:
{
if (next.isReferenceIdentifier()) {
return resolve(context, next, tokenizer, descriptor);
} else if (next.isSymbol(Token.kListStart)) {
@SuppressWarnings("unchecked") PSet<Object> ct = (PSet) descriptor;
Set<Object> value = new LinkedHashSet<>();
next = tokenizer.expect("set value or end");
while (!next.isSymbol(Token.kListEnd)) {
Object item = parseFieldValue(next, tokenizer, context, ct.itemDescriptor(), strict);
if (item != null) {
value.add(item);
}
// sets require separator, and allows separator after last.
if (tokenizer.expectSymbol("set separator or end", Token.kLineSep1, Token.kListEnd) == Token.kListEnd) {
break;
}
next = tokenizer.expect("set value or end");
}
return ct.builder().addAll(value).build();
}
break;
}
case LIST:
{
if (next.isReferenceIdentifier()) {
return resolve(context, next, tokenizer, descriptor);
} else if (next.isSymbol(Token.kListStart)) {
@SuppressWarnings("unchecked") PList<Object> ct = (PList) descriptor;
PList.Builder<Object> builder = ct.builder();
next = tokenizer.expect("list value or end");
while (!next.isSymbol(Token.kListEnd)) {
Object item = parseFieldValue(next, tokenizer, context, ct.itemDescriptor(), strict);
if (item != null) {
builder.add(item);
}
// lists require separator, and allows separator after last.
if (tokenizer.expectSymbol("list separator or end", Token.kLineSep1, Token.kListEnd) == Token.kListEnd) {
break;
}
next = tokenizer.expect("list value or end");
}
return builder.build();
}
break;
}
default:
{
throw new TokenizerException(next, descriptor.getType() + " not supported!").setLine(tokenizer.getLine());
}
}
} catch (ProvidenceConfigException e) {
throw new TokenizerException(next, e.getMessage()).setLine(tokenizer.getLine());
}
throw new TokenizerException(next, "Unhandled value \"%s\" for type %s", next.asString(), descriptor.getType()).setLine(tokenizer.getLine());
}
use of net.morimekta.providence.descriptor.PMessageDescriptor in project providence by morimekta.
the class OverrideConfigSupplier method builderForField.
private static PMessageBuilder builderForField(boolean strict, PMessageBuilder builder, String... path) throws ProvidenceConfigException {
for (int i = 0; i < (path.length - 1); ++i) {
PMessageDescriptor descriptor = builder.descriptor();
String fieldName = path[i];
PField field = descriptor.findFieldByName(fieldName);
if (field == null) {
if (strict) {
throw new ProvidenceConfigException("No such field %s in %s [%s]", fieldName, descriptor.getQualifiedName(), String.join(".", path));
}
return null;
}
if (field.getType() != PType.MESSAGE) {
throw new ProvidenceConfigException("'%s' is not a message field in %s [%s]", fieldName, descriptor.getQualifiedName(), String.join(".", path));
}
builder = builder.mutator(field.getId());
}
return builder;
}
Aggregations