blob: e59a972d066b546fdda726ac4367533ac9583412 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library discoveryapis_generator.uri_template;
import 'dart_api_library.dart';
import 'namer.dart';
import 'utils.dart';
/// Generates code for expanding a URI template.
abstract class Part {
final DartApiImports imports;
final String templateVar;
Part(this.imports, this.templateVar);
/// Generates a dart expression by expanding this part using [variable] as
/// the contests of a template variable.
String stringExpression(Identifier variable);
}
/// Represents a URI Template literal.
class StringPart extends Part {
final String staticString;
StringPart(DartApiImports imports, this.staticString) : super(imports, null);
@override
String stringExpression(Identifier variable) =>
"'${escapeString(staticString)}'";
}
/// Represents a URI Template variable expression of the form {var}
class VariableExpression extends Part {
VariableExpression(DartApiImports imports, String templateVar)
: super(imports, templateVar);
@override
String stringExpression(Identifier variable) =>
"${imports.commons}.Escaper.ecapeVariable('\$$variable')";
}
/// Represents a URI Template variable expression of the form {/var*}
class PathVariableExpression extends Part {
PathVariableExpression(DartApiImports imports, String templateVar)
: super(imports, templateVar);
@override
String stringExpression(Identifier variable) =>
"'/' + ($variable).map((item) => "
"${imports.commons}.Escaper.ecapePathComponent(item)).join('/')";
}
/// Represents a URI Template variable expression of the form {+var}
class ReservedExpansionExpression extends Part {
ReservedExpansionExpression(DartApiImports imports, String templateVar)
: super(imports, templateVar);
@override
String stringExpression(Identifier variable) =>
"${imports.commons}.Escaper.ecapeVariableReserved('\$$variable')";
}
/// Represents a URI Template as defined in RFC 6570.
///
/// This class supports only a very limited subset of RFC 6570, namely
/// the following expression types: {var}, {/var*} and {+var}.
///
/// See: http://tools.ietf.org/html/rfc6570
class UriTemplate {
final List<Part> parts;
UriTemplate(this.parts);
/// Generates a dart expression by expanding this [UriTemplate] using
/// [identifiers].
///
/// The key in [identifiers] are template variable names and the values are
/// the dart [Identifier]s which contain the dart value.
String stringExpression(Map<String, Identifier> identifiers) =>
parts.map((Part part) {
if (part.templateVar == null) {
return part.stringExpression(null);
}
final identifier = identifiers[part.templateVar];
if (identifier == null) {
throw ArgumentError(
'Could not find entry ${part.templateVar} in identifier map.');
}
return part.stringExpression(identifier);
}).join(' + ');
static UriTemplate parse(DartApiImports imports, String pattern) {
final parts = <Part>[];
var offset = 0;
while (offset < pattern.length) {
final open = pattern.indexOf('{', offset);
// If we have no more URI template expressions, we append the remaining
// string as a literal and we're done.
if (open < 0) {
final rest = pattern.substring(offset);
parts.add(StringPart(imports, rest));
break;
}
// We append the static string prefix as a literal (if necessary).
if (open > offset) {
final stringPrefix = pattern.substring(offset, open);
parts.add(StringPart(imports, stringPrefix));
}
final close = pattern.indexOf('}', open);
if (close < 0) {
throw ArgumentError('Invalid URI template pattern, '
"expected closing brace: '$pattern'");
}
// We extract the URI template expression and generate an expression
// object for it.
final templateExpression = pattern.substring(open + 1, close);
if (templateExpression.startsWith('/') &&
templateExpression.endsWith('*')) {
final variable =
templateExpression.substring(1, templateExpression.length - 1);
_ensureValidVariable(variable);
parts.add(PathVariableExpression(imports, variable));
} else if (templateExpression.startsWith('+')) {
final variable = templateExpression.substring(1);
_ensureValidVariable(variable);
parts.add(ReservedExpansionExpression(imports, variable));
} else {
final variable = templateExpression;
_ensureValidVariable(variable);
parts.add(VariableExpression(imports, variable));
}
offset = close + 1;
}
return UriTemplate(parts);
}
static void _ensureValidVariable(String name) {
final codeUnites = name.codeUnits;
for (var i = 0; i < codeUnites.length; i++) {
final char = codeUnites[i];
final isLetter =
(65 <= char && char <= 90) || (97 <= char && char <= 122);
final isNumber = 48 <= char && char <= 57;
final isUnderscore = char == 0x5F;
if (i == 0 && !isLetter) {
throw ArgumentError('Variables can only begin with an upper or '
'lowercase letter: "$name".');
}
if (!isLetter && !isNumber && !isUnderscore) {
throw ArgumentError('Variables can only consist of uppercase '
'letters, lowercase letters, underscores and '
'numbers: "$name".');
}
}
}
}